Jump to content

[Solved] [1.10.2] Client-side tile entity not saving on quit


IceMetalPunk

Recommended Posts

I have a block and an associated ITickable tile entity. Everything works fine, until I quit the world and re-load it. After that, the server-side tile entity loads up fine, but the client-side one seems to disappear. (Tested by having the tile entity's update() method output this.worldObj.isRemote to the system log. It outputs for both client and server after placing the block, but only for the server after reloading the world.)

 

I've organized my code in such a way as to try and make adding new blocks, items, etc. easier on myself later, so there's lots of inheritance and classes which register themselves on instantiation. I'm wondering if, while setting up that structure, I've missed something somewhere? Here's the relevant code:

 

ChaoticaBlockBase.java:

 

package com.icemetalpunk.chaotica.blocks;

import com.icemetalpunk.chaotica.Chaotica;

import net.minecraft.block.Block;
import net.minecraft.block.material.Material;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.fml.common.registry.GameRegistry;
import net.minecraftforge.oredict.OreDictionary;

public class ChaoticaBlockBase extends Block {

public ChaoticaBlockBase(String name, Material materialIn) {
	super(materialIn);
	this.setUnlocalizedName(name).setRegistryName(new ResourceLocation(Chaotica.MODID, name))
			.setCreativeTab(Chaotica.tab);
}

protected void register() {
	GameRegistry.register(this);
	if (this instanceof ChaoticaTEBlock) {
		GameRegistry.registerTileEntity(((ChaoticaTEBlock) this).getTileEntityClass(),
				((ChaoticaTEBlock) this).getTileEntityName());
	}

	String[] oreDict = this.getOreDict();
	if (oreDict != null) {
		for (String entry : oreDict) {
			OreDictionary.registerOre(entry, this);
		}
	}
}

// Override this if this block has an oredict entry.
public String[] getOreDict() {
	return null;
}

}

 

 

ChaoticaTEBlockBase.java (For tile entity providing blocks.)

 

package com.icemetalpunk.chaotica.blocks;

import net.minecraft.block.ITileEntityProvider;
import net.minecraft.block.material.Material;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.world.World;

public abstract class ChaoticaTEBlock extends ChaoticaBlockBase implements ITileEntityProvider {

public ChaoticaTEBlock(String name, Material materialIn) {
	super(name, materialIn);
}

// Tile entity providers should provide the class and name of their tile
// entity here for registration.
public abstract Class<? extends TileEntity> getTileEntityClass();

public abstract String getTileEntityName();

// Generic createNewTileEntity so only the getTileEntityClass needs to be
// specified.
@Override
public TileEntity createNewTileEntity(World world, int meta) {
	try {
		return this.getTileEntityClass().newInstance();
	}
	catch (InstantiationException e) {
		e.printStackTrace();
		return null;
	}
	catch (IllegalAccessException e) {
		e.printStackTrace();
		return null;
	}
}
}

 

 

Here's the specific block in question:

 

BlockChaoticCondenser.java:

 

package com.icemetalpunk.chaotica.blocks;

import javax.annotation.Nullable;

import com.icemetalpunk.chaotica.Chaotica;
import com.icemetalpunk.chaotica.gui.ChaoticaGuiHandler;
import com.icemetalpunk.chaotica.tileentities.TileEntityChaoticCondenser;

import net.minecraft.block.SoundType;
import net.minecraft.block.material.Material;
import net.minecraft.block.state.IBlockState;
import net.minecraft.entity.player.EntityPlayer;
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.world.World;

public class BlockChaoticCondenser extends ChaoticaTEBlock {

public BlockChaoticCondenser() {
	super("chaotic_condenser", Material.ROCK);
	this.setHardness(3.5F);
	this.setSoundType(SoundType.STONE);
	this.register();
}

@Override
public boolean onBlockActivated(World world, BlockPos pos, IBlockState state, EntityPlayer player, EnumHand hand,
		@Nullable ItemStack item, EnumFacing side, float hitX, float hitY, float hitZ) {
	if (!world.isRemote) {
		player.openGui(Chaotica.instance, ChaoticaGuiHandler.Guis.CONDENSER.ordinal(), world, pos.getX(),
				pos.getY(), pos.getZ());
	}
	return true;
}

@Override
public Class<? extends TileEntity> getTileEntityClass() {
	return TileEntityChaoticCondenser.class;
}

@Override
public String getTileEntityName() {
	return "ChaoticCondenser";
}

}

 

 

And the tile entity, or at least the relevant code:

 

TileEntityChaoticCondenser.java:

 

package com.icemetalpunk.chaotica.tileentities;

import java.util.Iterator;
import java.util.Map;

import com.icemetalpunk.chaotica.Chaotica;
import com.icemetalpunk.chaotica.ChaoticaUtils;
import com.icemetalpunk.chaotica.fluids.FluidTankChaos;
import com.icemetalpunk.chaotica.sounds.ChaoticaSoundRegistry;

import net.minecraft.block.Block;
import net.minecraft.block.state.IBlockState;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.inventory.IInventory;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.network.NetworkManager;
import net.minecraft.network.play.server.SPacketUpdateTileEntity;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.ITickable;
import net.minecraft.util.SoundCategory;
import net.minecraftforge.fluids.Fluid;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.fluids.capability.FluidTankPropertiesWrapper;
import net.minecraftforge.fluids.capability.IFluidHandler;
import net.minecraftforge.fluids.capability.IFluidTankProperties;

public class TileEntityChaoticCondenser extends TileEntity implements IFluidHandler, ITickable {

protected Fluid fluid = Chaotica.fluids.CORROSIVE_CHAOS;
protected int capacity = 5 * Fluid.BUCKET_VOLUME;
protected FluidStack fluidStack = new FluidStack(this.fluid, 0);
protected FluidTankChaos tank = new FluidTankChaos(this.capacity);
protected int countdown = 40;
protected int maxCountdown = 40; // Max amount it resets to

public TileEntityChaoticCondenser() {
	this.tank.setCanFill(false);
}

// Ticks until the next check for blocks to convert to chaos
public int getCountdown() {
	return this.countdown;
}

public int getMaxCountdown() {
	return this.maxCountdown;
}

@Override
public IFluidTankProperties[] getTankProperties() {
	return new IFluidTankProperties[] { new FluidTankPropertiesWrapper(tank) };
};

public Fluid getFluid() {
	return this.fluidStack.getFluid();
}

@Override
public void readFromNBT(NBTTagCompound tag) {
	super.readFromNBT(tag);
	NBTTagCompound tankTag = tag.getCompoundTag("Tank");
	this.tank.readFromNBT(tankTag);
	this.countdown = tag.getInteger("Countdown");
}

@Override
public NBTTagCompound writeToNBT(NBTTagCompound tag) {
	super.writeToNBT(tag);
	NBTTagCompound tankTag = new NBTTagCompound();
	tank.writeToNBT(tankTag);
	tag.setTag("Tank", tankTag);
	tag.setInteger("Countdown", this.countdown);
	return tag;
}

public int fill(int amount, boolean doFill) {
	return this.tank.fill(new FluidStack(this.fluid, amount), doFill);
}

@Override
public int fill(FluidStack resource, boolean doFill) {
	return this.tank.fill(resource, doFill);
}

@Override
public FluidStack drain(FluidStack resource, boolean doDrain) {
	return this.tank.drain(resource, doDrain);
}

@Override
public FluidStack drain(int maxDrain, boolean doDrain) {
	return this.tank.drain(maxDrain, doDrain);
}

@Override
public void update() {
	String[] debug = new String[] { "server", "client" };
	if (--this.countdown == 0) {
		this.countdown = this.maxCountdown;
		System.out.println("Countdown on " + debug[this.worldObj.isRemote ? 1 : 0] + " to " + this.countdown);
		// ...snipped out irrelevant code here...
	}
}

@Override
public SPacketUpdateTileEntity getUpdatePacket() {
	NBTTagCompound tag = new NBTTagCompound();
	this.writeToNBT(tag);

	IBlockState state = this.worldObj.getBlockState(this.pos);
	Block block = state.getBlock();
	int metadata = block.getMetaFromState(state);

	return new SPacketUpdateTileEntity(this.pos, metadata, tag);
}

@Override
public void onDataPacket(NetworkManager net, SPacketUpdateTileEntity packet) {
	this.readFromNBT(packet.getNbtCompound());
}

}

 

 

If you look in the tile entity's update() method, you'll see the simple debug output I'm using. So why does it all work fine when I place the block, but after reloading the world, all the messages from the client-side tile entity stop, as though it's just removed and not re-created when the world loads again?

Whatever Minecraft needs, it is most likely not yet another tool tier.

Link to comment
Share on other sites

In 1.10.2 TE's also need to override

TileEntity#getUpdateTag()

. There is also a corresponding

TileEntity#handleUpdateTag()

method, which you will need to override if you want to do anything other than calling

TileEntity#readFromNBT

(This is what the default implementation does). I don't know for sure that this is your problem, but trying doesn't hurt. These methods are certainly involved in client-server communications though.

Link to comment
Share on other sites

It's not directly related to your question, but you're using

IFluidHandler

incorrectly.

 

The whole point of the Capability system is that you don't implement interfaces on your

TileEntity

, instead you store the objects in the

TileEntity

and override the

ICapabilityProvider

methods to return them. Forge's documentation explains this in more detail here.

Please don't PM me to ask for help. Asking your question in a public thread preserves it for people who are having the same problem in the future.

Link to comment
Share on other sites

Also don't implement ITileEntityProvider.  You want the hasTileEntity and getTileEntity methods that exist in the Block class (hint: the one you're using is wrong).

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

Also don't implement ITileEntityProvider.  You want the hasTileEntity and getTileEntity methods that exist in the Block class (hint: the one you're using is wrong).

 

Okay, I've switched to using the ones from the Block class; but it seems like the default implementations of those methods check for ITileEntityProvider anyway and ultimately end up doing the same thing; what's the benefit to switching over?

 

It's not directly related to your question, but you're using

IFluidHandler

incorrectly.

 

The whole point of the Capability system is that you don't implement interfaces on your

TileEntity

, instead you store the objects in the

TileEntity

and override the

ICapabilityProvider

methods to return them. Forge's documentation explains this in more detail here.

 

I'll admit, after reading the documentation for Capabilities, as well as some example code elsewhere...I'm totally confused by the system. So basically, I would attach an instance of the IFluidHandler Capability to the tile entity in the AttachCapabilities event, one instance per tank in the tile entity, then override the tile entity's getCapability/hasCapability methods to return those fluid handler instances? And then whenever it needs to fill/drain anything, it would...uh, pick one of the capabilities and call the fill/drain methods on them? I really don't understand what's going on there, nor do I understand what the system is supposed to improve over simply implementing the handler interface on the tile entity itself...

Whatever Minecraft needs, it is most likely not yet another tool tier.

Link to comment
Share on other sites

I'll admit, after reading the documentation for Capabilities, as well as some example code elsewhere...I'm totally confused by the system. So basically, I would attach an instance of the IFluidHandler Capability to the tile entity in the AttachCapabilities event, one instance per tank in the tile entity, then override the tile entity's getCapability/hasCapability methods to return those fluid handler instances? And then whenever it needs to fill/drain anything, it would...uh, pick one of the capabilities and call the fill/drain methods on them? I really don't understand what's going on there, nor do I understand what the system is supposed to improve over simply implementing the handler interface on the tile entity itself...

 

Only use

AttachCapabilitiesEvent

to attach capabilities to external objects. For your own objects, simply store the capability instance in a field and override the

ICapabilityProvider

methods.

 

Let's say your

TileEntity

has a fluid tank, so it stores an instance of

FluidTank

in a field. If you want to expose that tank as a capability (so other mods can interact with it), override the following methods:

  • hasCapability

    : Return

    true

    if the

    Capability

    argument is

    CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY

    and the

    EnumFacing

    is correct.

  • getCapability

    : Return the

    FluidTank

    instance if the

    Capability

    argument is

    CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY

    and the

    EnumFacing

    is correct. Use

    Capability#cast

    to cast the object you return from this method to the correct type (this is to work around the limitations of generics, Java doesn't know that the object you're returning is an instance of the generic return type).

 

For both methods, return the result of the super method if the

Capability

or

EnumFacing

isn't one that you handle.

 

If the tank can be accessed from all sides, you can ignore the

EnumFacing

argument entirely.

 

Some of the main advantages of the capability system are the ability to attach your own capabilities to external objects (e.g. give a vanilla item a fluid tank or inventory) and the ability to split up your code into multiple classes and use existing classes (e.g. you create a

TileEntity

that uses the existing

FluidTank

and

ItemStackHandler

classes instead of a

TileEntity

that implements

IFluidHandler

and

IItemHandler

itself).

Please don't PM me to ask for help. Asking your question in a public thread preserves it for people who are having the same problem in the future.

Link to comment
Share on other sites

Okay, I've switched to using the ones from the Block class; but it seems like the default implementations of those methods check for ITileEntityProvider anyway and ultimately end up doing the same thing; what's the benefit to switching over?

 

Its only still there for compatibility on things that haven't been updated to the new style yet.  The method you're using takes in a metadata value, not a blockstate, thus is out of date.

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

...snip...

Thank you for your clear explanation! I've switched over to the Capability system for my tile entity now, and everything still works, which I'll take as a good sign. I can certainly see the advantage of better mod interoperability. Now if only I could get the fluid tank rendering to work properly...but that's a different bug for a different day, and one for me to try and figure out first before asking! :D Thanks again.

Whatever Minecraft needs, it is most likely not yet another tool tier.

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.