Jump to content

[Solved][1.12.2] Items are not saved in custom TileEntity with an inventory


xerca

Recommended Posts

Hello, I am trying to update my mod from 1.9 to 1.12.2, and although I encountered a lot of problems, I was able to solve most of them, except for this one. I read a thousand forum threads, tutorials and repositories like this but I just can't seem to fix it.

 

I have a block with a tile entity that has an inventory, similar to a chest. It only allows certain items to be stored, and changes texture depending on the number of item slots filled inside. The problem is as such:

* When I right click the block, gui opens as expected

* When I try to put an item in one of the slots, it works as expected (the itemstack is moved in the slot, and only certain item types are allowed)

* When I close the gui, I can see that the texture of the block has updated according to the amount of itemstacks put in it as expected.

* When I open it back, the item(s) put beforehand are not there anymore. But this is only most of the time. Sometimes the item(s) will remain, but if I put something else, they are gone next time.

* After I close the gui, I can see that the texture also reverted to the zero item texture.

 

I updated everything about it that I can. I learned about the IItemHandler stuff and converted all my old IInventory based code into the new system. I used the new registry function for the tile entity and put the function call in the block registry event handler. I stopped extending BlockContainer, etc, etc. I also added an excessive amount of TileEntity::markDirty() calls to make sure that wasn't the problem (it isn't). Here are the relevant files:

 

public class TileEntityFunctionalBookcase extends TileEntity {
    private final static int NUMBER_OF_SLOTS = 6;
    private final ItemStackHandler inventory;

	public TileEntityFunctionalBookcase(){
        // Create and initialize the items variable that will store store the items
        inventory = new ItemStackHandler(NUMBER_OF_SLOTS){
            protected void onContentsChanged(int slot)
            {
                markDirty();
            }
        };
    }

	public int getSizeInventory() {
		return NUMBER_OF_SLOTS;
	}

    // Return true if the given player is able to use this block. In this case it checks that
	// 1) the world tileentity hasn't been replaced in the meantime, and
	// 2) the player isn't too far away from the centre of the block
	public boolean isUsableByPlayer(EntityPlayer player) {
		if (this.world.getTileEntity(this.pos) != this) return false;
		final double X_CENTRE_OFFSET = 0.5;
		final double Y_CENTRE_OFFSET = 0.5;
		final double Z_CENTRE_OFFSET = 0.5;
		final double MAXIMUM_DISTANCE_SQ = 8.0 * 8.0;
		return player.getDistanceSq(pos.getX() + X_CENTRE_OFFSET, pos.getY() + Y_CENTRE_OFFSET, pos.getZ() + Z_CENTRE_OFFSET) < MAXIMUM_DISTANCE_SQ;
	}

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

    @Override
    @Nullable
    public <T> T getCapability(@Nonnull Capability<T> capability, @Nullable net.minecraft.util.EnumFacing facing)
    {
        if (capability == CapabilityItemHandler.ITEM_HANDLER_CAPABILITY) {
            return CapabilityItemHandler.ITEM_HANDLER_CAPABILITY.cast(inventory);
        }
        return super.getCapability(capability, facing);
    }

    @Nonnull
	@Override
	public NBTTagCompound writeToNBT(NBTTagCompound parentNBTTagCompound)
	{
		super.writeToNBT(parentNBTTagCompound); // The super call is required to save and load the tileEntity's location
        NBTTagCompound inventoryTagCompound = this.inventory.serializeNBT();
        parentNBTTagCompound.setTag("inventory", inventoryTagCompound);
        System.out.println("Write to NBT");
        System.out.println(inventoryTagCompound);
		return parentNBTTagCompound;
	}

	@Override
	public void readFromNBT(NBTTagCompound parentNBTTagCompound)
	{
		super.readFromNBT(parentNBTTagCompound); // The super call is required to save and load the tiles location
        NBTTagCompound inventoryTagCompound = parentNBTTagCompound.getCompoundTag("inventory");
		this.inventory.deserializeNBT(inventoryTagCompound);
        System.out.println("Read from NBT");
        System.out.println(inventoryTagCompound);
	}

	public void closeInventory(EntityPlayer player) {
		int i = getBookAmount();
		IBlockState st = XercaBlocks.blockBookcase.getDefaultState().withProperty(BlockFunctionalBookcase.BOOK_AMOUNT, i);
		this.world.setBlockState(this.pos, st);
		this.markDirty();
	}

	@Override
	public boolean shouldRefresh(World world, BlockPos pos, @Nonnull IBlockState oldState, @Nonnull IBlockState newSate)
    {
        return oldState != newSate;
    }

	private int getBookAmount(){
		int total = 0;
		for(int i=0; i<this.inventory.getSlots(); i++){
			if(!this.inventory.getStackInSlot(i).isEmpty()){
				total++;
			}
		}
		return total;
	}

	@Override
	public void markDirty() {
		super.markDirty();
//		System.out.println("Marked dirty");
	}
}
public class BlockFunctionalBookcase extends Block {

	public static final PropertyInteger BOOK_AMOUNT = PropertyInteger.create("books", 0, 6);
	
	public BlockFunctionalBookcase()
	{
		super(Material.WOOD);
		this.setDefaultState(this.blockState.getBaseState().withProperty(BOOK_AMOUNT, 0));
		this.setRegistryName("block_bookcase");
		this.setUnlocalizedName("block_bookcase");
		this.setCreativeTab(CreativeTabs.DECORATIONS);
	}



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

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


	// Called when the block is right clicked
	// In this block it is used to open the blocks gui when right clicked by a player
	@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) return true;

		playerIn.openGui(XercaMod.instance, GuiFunctionalBookcase.GUI_ID, worldIn, pos.getX(), pos.getY(), pos.getZ());
		return true;
	}

	// This is where you can do something when the block is broken. In this case drop the inventory's contents
	@Override
	public void breakBlock(World worldIn, BlockPos pos, IBlockState state) {
		TileEntity tent = worldIn.getTileEntity(pos);
		if(tent.hasCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY, null)){
			IItemHandler inventory = tent.getCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY, null);

			if (inventory != null){
				// For each slot in the inventory
				for (int i = 0; i < inventory.getSlots(); i++){
					// If the slot is not empty
					if (!inventory.getStackInSlot(i).isEmpty())
					{
						// Create a new entity item with the item stack in the slot
						EntityItem item = new EntityItem(worldIn, pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5, inventory.getStackInSlot(i));

						// Apply some random motion to the item
						float multiplier = 0.1f;
						float motionX = worldIn.rand.nextFloat() - 0.5f;
						float motionY = worldIn.rand.nextFloat() - 0.5f;
						float motionZ = worldIn.rand.nextFloat() - 0.5f;

						item.motionX = motionX * multiplier;
						item.motionY = motionY * multiplier;
						item.motionZ = motionZ * multiplier;

						// Spawn the item in the world
						worldIn.spawnEntity(item);
					}
				}
			}
		}

		// Super MUST be called last because it removes the tile entity
		super.breakBlock(worldIn, pos, state);
	}

	//---------------------------------------------------------

	  @Override
	  public IBlockState getStateFromMeta(int meta)
	  {
		  return this.getDefaultState().withProperty(BOOK_AMOUNT, meta);
	  }
	
	  @Override
	public int getMetaFromState(IBlockState state)
	{
		return state.getValue(BOOK_AMOUNT);
	}
	
	protected BlockStateContainer createBlockState()
	{
	    return new BlockStateContainer(this, BOOK_AMOUNT);
	}
	
	@Override
	public EnumBlockRenderType getRenderType(IBlockState state)
    {
        return EnumBlockRenderType.MODEL;
    }

}
public class ContainerFunctionalBookcase extends Container {
	private final TileEntityFunctionalBookcase tileEntityInventoryBookcase;

    public ContainerFunctionalBookcase(InventoryPlayer invPlayer, TileEntityFunctionalBookcase tileEntityInventoryBookcase) {
		this.tileEntityInventoryBookcase = tileEntityInventoryBookcase;
		IItemHandler inventory = tileEntityInventoryBookcase.getCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY, null);

		final int SLOT_X_SPACING = 18;
		final int SLOT_Y_SPACING = 18;
		final int HOTBAR_XPOS = 8;
		final int HOTBAR_YPOS = 142;
		// Add the players hotbar to the gui - the [xpos, ypos] location of each item
        int HOTBAR_SLOT_COUNT = 9;
        for (int x = 0; x < HOTBAR_SLOT_COUNT; x++) {
			addSlotToContainer(new Slot(invPlayer, x, HOTBAR_XPOS + SLOT_X_SPACING * x, HOTBAR_YPOS));
		}

		final int PLAYER_INVENTORY_XPOS = 8;
		final int PLAYER_INVENTORY_YPOS = 84;
		// Add the rest of the players inventory to the gui
        int PLAYER_INVENTORY_ROW_COUNT = 3;
        for (int y = 0; y < PLAYER_INVENTORY_ROW_COUNT; y++) {
            int PLAYER_INVENTORY_COLUMN_COUNT = 9;
            for (int x = 0; x < PLAYER_INVENTORY_COLUMN_COUNT; x++) {
				int slotNumber = HOTBAR_SLOT_COUNT + y * PLAYER_INVENTORY_COLUMN_COUNT + x;
				int xpos = PLAYER_INVENTORY_XPOS + x * SLOT_X_SPACING;
				int ypos = PLAYER_INVENTORY_YPOS + y * SLOT_Y_SPACING;
				addSlotToContainer(new Slot(invPlayer, slotNumber,  xpos, ypos));
			}
		}

        int TE_INVENTORY_SLOT_COUNT = 6;
        if (TE_INVENTORY_SLOT_COUNT != tileEntityInventoryBookcase.getSizeInventory()) {
			System.err.println("Mismatched slot count in ContainerFunctionalBookcase(" + TE_INVENTORY_SLOT_COUNT
												  + ") and TileEntityFunctionalBookcase (" + tileEntityInventoryBookcase.getSizeInventory()+")");
		}
		final int TILE_INVENTORY_XPOS = 61;
		final int TILE_INVENTORY_YPOS = 17;
		final int TILE_SLOT_Y_SPACING = 32;
		final int TILE_ROW_COUNT = 2;
		final int TILE_COLUMN_COUNT = 3;
		
		// Add the tile inventory container to the gui
		for (int y = 0; y < TILE_ROW_COUNT; y++) {
			for (int x = 0; x < TILE_COLUMN_COUNT; x++) {
				int slotNumber = y * TILE_COLUMN_COUNT + x;
				int xpos = TILE_INVENTORY_XPOS + x * SLOT_X_SPACING;
				int ypos = TILE_INVENTORY_YPOS + y * TILE_SLOT_Y_SPACING;
				addSlotToContainer(new SlotBook(inventory, slotNumber,  xpos, ypos));
			}
		}
	}

	@Override
	public boolean canInteractWith(@Nonnull EntityPlayer player)
	{
		return tileEntityInventoryBookcase.isUsableByPlayer(player);
	}

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

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

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

			if (sourceSlotIndex < 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;
	}

	@Override
	public void onContainerClosed(EntityPlayer playerIn)
	{
		super.onContainerClosed(playerIn);
		this.tileEntityInventoryBookcase.closeInventory(playerIn);
	}

	class SlotBook extends SlotItemHandler {
		SlotBook(IItemHandler itemHandler, int index, int xPosition, int yPosition) {
			super(itemHandler, index, xPosition, yPosition);
		}

		// if this function returns false, the player won't be able to insert the given item into this slot
		@Override
		public boolean isItemValid(@Nonnull ItemStack stack) {
			Item it = stack.getItem();
			return it == Items.BOOK || it == Items.WRITABLE_BOOK || it == Items.WRITTEN_BOOK || it == Items.ENCHANTED_BOOK;
		}

		@Override
		public void onSlotChanged() {
			tileEntityInventoryBookcase.markDirty();
		}
	}
}
@SideOnly(Side.CLIENT)
public class GuiFunctionalBookcase extends GuiContainer {
	public static final int GUI_ID = 30;
	private InventoryPlayer playerInv;

	private static final ResourceLocation texture = new ResourceLocation(XercaMod.MODID, "textures/gui/bookcase.png");

	public GuiFunctionalBookcase(InventoryPlayer invPlayer, ContainerFunctionalBookcase container) {
		super(container);
		playerInv = invPlayer;
		// Set the width and height of the gui.  Should match the size of the texture!
		xSize = 176;
		ySize = 166;
	}

	@Override
	protected void drawGuiContainerBackgroundLayer(float partialTicks, int x, int y) {
		// Bind the image texture of our custom container
		Minecraft.getMinecraft().getTextureManager().bindTexture(texture);
		// Draw the image
		GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F);
		drawTexturedModalRect(guiLeft, guiTop, 0, 0, xSize, ySize);
	}

	@Override
	protected void drawGuiContainerForegroundLayer(int mouseX, int mouseY) {
		final int LABEL_XPOS = 5;
		final int LABEL_YPOS = 5;
		//fontRendererObj.drawString(tileEntityInventoryBookcase.getDisplayName().getUnformattedText(), LABEL_XPOS, LABEL_YPOS, Color.darkGray.getRGB());
	}
}
public class XercaBlocks {
	public static Block blockBookcase;

	...

	@Mod.EventBusSubscriber(modid = XercaMod.MODID)
	public static class RegistrationHandler {
		@SubscribeEvent
		public static void registerBlocks(final RegistryEvent.Register<Block> event) {
			event.getRegistry().registerAll(blockBookcase, ...(other blocks));

            GameRegistry.registerTileEntity(TileEntityFunctionalBookcase.class, new ResourceLocation(XercaMod.MODID, "tile_functional_bookcase"));
		}
        ...
	}
}

 

If anyone can help, I will really appreciate it. There is probably a stupid mistake somewhere that I just can't see, but it has been a few days already and I still can't solve this! Thanks in advance.

Edited by xerca
Included version in title
Link to comment
Share on other sites

11 minutes ago, veesus mikel heir said:

Did you remember to make and register an IGuiHandler?

Yes. It was in the old version, too (1.9).

public class XercaGuiHandler implements IGuiHandler {

	// Gets the server side element for the given gui id- this should return a container
	@Override
	public Object getServerGuiElement(int ID, EntityPlayer player, World world, int x, int y, int z) {
		if (ID == GuiFunctionalBookcase.GUI_ID){
			TileEntity tileEntity = world.getTileEntity(new BlockPos(x, y, z));
			if (tileEntity instanceof TileEntityFunctionalBookcase) {
				return new ContainerFunctionalBookcase(player.inventory, (TileEntityFunctionalBookcase) tileEntity);
			}
		}
		...
	}

	// Gets the client side element for the given gui id- this should return a gui
	@Override
	public Object getClientGuiElement(int ID, EntityPlayer player, World world, int x, int y, int z) {
		if (ID == GuiFunctionalBookcase.GUI_ID){
			TileEntity tileEntity = world.getTileEntity(new BlockPos(x, y, z));
			if (tileEntity instanceof TileEntityFunctionalBookcase) {
				return new GuiFunctionalBookcase(player.inventory, (ContainerFunctionalBookcase) getServerGuiElement(ID, player, world, x, y, z));
			}
		}
		...
	}
}
public class CommonProxy {
	...
	public void preInit()
	{
		...
		NetworkRegistry.INSTANCE.registerGuiHandler(XercaMod.instance, new XercaGuiHandler());
		...
	}
	...
}

 

Link to comment
Share on other sites

35 minutes ago, Animefan8888 said:

@xerca The best advice I can give you would be to step through with the debugger.


Do you have any idea where I should be checking? My first instinct was to put breakpoints in readFromNBT() and writeToNBT() methods, but after comparing with vanilla TileEntityChest, they seem to be called normally (write gets called when I press ESC or sometimes randomly and read gets called once in the beginning) but the data that is written is usually of an empty inventory. It is the data that comes from ItemStackHandler::serializeNBT().

 

So I don't think it has to do with NBT. But I don't know what it has to do with. So, I don't even know where to step thtough with the debugger ?. Also I don't know what should be happening in the normal case and I don't have a good reference to compare to because the vanilla chest code doesn't use Forge's capability system.

 

Could it be related to server-client communication? I read that it is supposed to be happening in the Container class somewhere, but I don't know where it should be or how it works, and none of the examples use any explicit code for communication either.

Link to comment
Share on other sites

IMO When trying to update a mod, you should usually not copy paste your old code then try & update it. You should start writing what your old code was trying (and hopefully succeeding) to do, and then copy/paste any code you KNOW works perfectly and hasn’t changed in between versions. Use your old code as a suggestion, not as a guide and not as something that should work. A lot has changed in between versions, and while your old code may appear to work, there is probably a new & better way of doing what your old code did

About Me

Spoiler

My Discord - Cadiboo#8887

My WebsiteCadiboo.github.io

My ModsCadiboo.github.io/projects

My TutorialsCadiboo.github.io/tutorials

Versions below 1.14.4 are no longer supported on this forum. Use the latest version to receive support.

When asking support remember to include all relevant log files (logs are found in .minecraft/logs/), code if applicable and screenshots if possible.

Only download mods from trusted sites like CurseForge (minecraft.curseforge.com). A list of bad sites can be found here, with more information available at stopmodreposts.org

Edit your own signature at www.minecraftforge.net/forum/settings/signature/ (Make sure to check its compatibility with the Dark Theme)

Link to comment
Share on other sites

I managed to solve the problem. Apparently the TileEntity::shouldRefresh() method which returns true if the blockstate is changed, causes the TileEntity to be deleted and recreated. So, whenever I added/removed an item to/from a slot I would change the blockstate to reflect it on the texture, which would cause it to delete the whole inventory. Now I override the method to return false in any case and it works correctly (maybe I should check if the block is completely changed, not sure).

 

Thanks to anyone who tried to help.

Edited by xerca
Link to comment
Share on other sites

44 minutes ago, xerca said:

return false

You should return true when the Blocks are different.

VANILLA MINECRAFT CLASSES ARE THE BEST RESOURCES WHEN MODDING

I will be posting 1.15.2 modding tutorials on this channel. If you want to be notified of it do the normal YouTube stuff like subscribing, ect.

Forge and vanilla BlockState generator.

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.

Announcements



×
×
  • Create New...

Important Information

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