Jump to content

[Any] [SOLVED] Saving NBT data


Bektor

Recommended Posts

Hi,

 

I've got a few problems with saving NBT data:

 

  1. How can I write and save NBT data when the corresponding value is null?
  2. I've got a machine which should store the energy it has, but on world reload the containing value resets itself to 0.

 

Corresponding code to 1:

Note that the value facing might be null.

   private HashMap<BlockPos, EnumFacing> connected = new HashMap<>();

    @Override
    public void readFromNBT(NBTTagCompound compound) {
        super.readFromNBT(compound);
    	if(compound.hasKey("connection")) {
            NBTTagList list = compound.getTagList("connection", Constants.NBT.TAG_COMPOUND);
            for(int i = 0; i <= list.tagCount(); i++) {
                NBTTagCompound com = list.getCompoundTagAt(i);
                if(com.hasKey("posX") && com.hasKey("posY") && com.hasKey("posZ")
                        && com.hasKey("facing")) {
                    
                    this.connected.put(
                            new BlockPos(
                                    com.getInteger("posX"),
                                    com.getInteger("posY"),
                                    com.getInteger("posZ")), 
                            EnumFacing.byName(com.getString("facing"))); // can be null!!!!
                }
            }
        }
    }
    
    @Override
    public NBTTagCompound writeToNBT(NBTTagCompound compound) {
    	NBTTagList list = new NBTTagList();
        this.connected.forEach((pos, side) -> {
            NBTTagCompound com = new NBTTagCompound();
            com.setInteger("posX", pos.getX());
            com.setInteger("posY", pos.getY());
            com.setInteger("posZ", pos.getZ());
            com.setString("facing", side.getName()); // can be null!!!
            list.appendTag(com);
        });
        compound.setTag("connection", list);
        
        return super.writeToNBT(compound);
    }

 

 

The second problem with NBT data:

    @Override
    public void readFromNBT(NBTTagCompound compound) {
        this.ownerId = compound.getUniqueId(TileEntityChunkLoader.UUID_TAG);
        
        super.readFromNBT(compound);
    }
    
    @Override
    public NBTTagCompound writeToNBT(NBTTagCompound compound) {
        compound.setUniqueId(TileEntityChunkLoader.UUID_TAG, this.ownerId);
        
        return super.writeToNBT(compound);
    }

And the corresponding base class from which the read and write methods are overridden:

    public final EnergyManager container;

    @Override
    public void readFromNBT(NBTTagCompound compound) {
        super.readFromNBT(compound);
        this.container.deserializeNBT(compound.getCompoundTag("Energy"));
    }
    
    @Override
    public NBTTagCompound writeToNBT(NBTTagCompound compound) {
        compound.setTag("Energy", this.container.serializeNBT());
        return super.writeToNBT(compound);
    }
Spoiler

Full code for the second NBT problem:

machine (java file) 

TileEntityEnergy.java (base class)

 

Thx in advance.

Bektor

Edited by Bektor

Developer of Primeval Forest.

Link to comment
Share on other sites

3 hours ago, Bektor said:

How can I write and save NBT data when the corresponding value is null?

Don't write anything, and check if the key is present in your read method(NBTTagCompound::hasKey). Then if the key is not found you know the value was null upon serializing and you can work around that.

 

3 hours ago, Bektor said:

The second problem with NBT data:

Upon investigating your code I notice that you do not have a default(parameterless) constructor for your TE and I am pretty sure that MC invokes the parameterless one when reading TEs from the disk. I suspect that because of that it can't be invoked and an exception is thrown thus preventing your TE from deserializing and the new one is created on demand with it's energy storage being empty.

Link to comment
Share on other sites

6 hours ago, V0idWa1k3r said:

Upon investigating your code I notice that you do not have a default(parameterless) constructor for your TE and I am pretty sure that MC invokes the parameterless one when reading TEs from the disk.

6 hours ago, V0idWa1k3r said:

exception is thrown thus preventing your TE from deserializing

I didn't noticed any exceptions thrown on saving the world thus far, thought I will check it later on.

 

EDIT: Ok. There are exceptions on loading the game. Thought adding an empty constructor won't fix the problem. No exceptions any more, but the energy value resets to 0.

Am I also required to save all fields of the EnergyManager to NBT data? ( capacity, maxReceive, maxExtract, shouldExplode etc.)

I mean, I've already got the fields set in the Block class which calls the constructor with parameters.

Edited by Bektor

Developer of Primeval Forest.

Link to comment
Share on other sites

11 hours ago, Bektor said:

I didn't noticed any exceptions thrown on saving...

Loading*

You mean loading.

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

12 hours ago, Bektor said:

Thought adding an empty constructor won't fix the problem. No exceptions any more, but the energy value resets to 0.

Well, post your new code then. Your EnergyManager will set the energy to capacity shall the energy be less than capacity and capacity is 0 by default. You used to initialize the capacity in your constructor based on the parameters but you can't do that in the parameterless constructor as there are no parameters present, duh. 

You only need to serialize enough data to be able to successfully create an identical object upon deserialization. 

 

12 hours ago, Bektor said:

I mean, I've already got the fields set in the Block class which calls the constructor with parameters.

Yes, the block does. The TileEntity.create method that is responsible for re-creating your TE when the chunk is loaded from the disk doesn't. That's what you need the default(parameterless) constructor for.

 

Edited by V0idWa1k3r
Link to comment
Share on other sites

5 hours ago, V0idWa1k3r said:

You only need to serialize enough data to be able to successfully create an identical object upon deserialization. 

 

Ok, I just serialized now all values from the EnergyStorage as my EnergyManager is based on EnergyStorage, but the problem still remains.

 

        private final int consume;
    private boolean isActive = false;
    
    @Nullable
    private UUID ownerId = null;
    private static final String UUID_TAG = "UUID";
    
    public TileEntityChunkLoader() {
    	// this has to be here in order for correct saving and loading of NBT data
    	this.consume = 0;
	}
    
    public TileEntityChunkLoader(int capacity, int maxTransfer) {
        super(capacity, maxTransfer, false);
        
        this.consume = maxTransfer;
        this.getEnergyManager().setTransferMode(EnergyTransfer.CONSUMER);
    }

    @Override
    public void update() {
        super.update();
        
        if(!this.hasWorld() || this.getWorld().isRemote)
            return;
        
        if(!this.isActive) { // isActive is currently always false, will change when logic is applied
            this.getWorld().notifyBlockUpdate(this.getPos(), this.getWorld().getBlockState(this.getPos()), this.getWorld().getBlockState(this.getPos()), 3);
            this.markDirty();
        }
    }

    @Override
    public SPacketUpdateTileEntity getUpdatePacket() {
        return new SPacketUpdateTileEntity(this.getPos(), 3, this.getUpdateTag());
    }
    
    @Override
    public NBTTagCompound getUpdateTag() {
        return this.writeToNBT(new NBTTagCompound());
    }
    
    @Override
    public void onDataPacket(NetworkManager net, SPacketUpdateTileEntity pkt) {
        super.onDataPacket(net, pkt);
        this.handleUpdateTag(pkt.getNbtCompound());
    }
    
    @Override
    public void readFromNBT(NBTTagCompound compound) {
        //this.ownerId = compound.getUniqueId(TileEntityChunkLoader.UUID_TAG);
        
        super.readFromNBT(compound);
    }
    
    @Override
    public NBTTagCompound writeToNBT(NBTTagCompound compound) {
        //compound.setUniqueId(TileEntityChunkLoader.UUID_TAG, this.ownerId); keeps crashing here (NPE)... :(
        
        return super.writeToNBT(compound);
    }

 

TileEntityEnergy

    public final EnergyManager container;
    
    public TileEntityEnergy() {
    	this.container = new EnergyManager(0, 0);
	}
    
    public TileEntityEnergy(int capacity, int maxTransfer) {
        this.container = new EnergyManager(capacity, maxTransfer);
    }
    
    public TileEntityEnergy(int capacity, int maxTransfer, boolean shouldExplode) {
        this.shouldExplode = shouldExplode;
        this.container = new EnergyManager(capacity, maxTransfer);
    }
    
    public TileEntityEnergy(int capacity, int maxReceive, int maxExtract) {
        this.container = new EnergyManager(capacity, maxReceive, maxExtract);
    }
    
    public TileEntityEnergy(int capacity, int maxReceive, int maxExtract, boolean shouldExplode) {
        this.shouldExplode = shouldExplode;
        this.container = new EnergyManager(capacity, maxReceive, maxExtract);
    }

    @Override
    public void readFromNBT(NBTTagCompound compound) {
        super.readFromNBT(compound);
        this.container.deserializeNBT(compound.getCompoundTag("Energy"));
    }
    
    @Override
    public NBTTagCompound writeToNBT(NBTTagCompound compound) {
        compound.setTag("Energy", this.container.serializeNBT());
        return super.writeToNBT(compound);
    }

 

EnergyManager:

    @Override
    public NBTTagCompound serializeNBT() {
        final NBTTagCompound nbtTagCompound = new NBTTagCompound();
        
        nbtTagCompound.setInteger("energy", this.energy);
        nbtTagCompound.setInteger("capacity", this.capacity);
        nbtTagCompound.setInteger("extract", this.maxExtract);
        nbtTagCompound.setInteger("receive", this.maxReceive);
        
        return nbtTagCompound;
    }
    
    @Override
    public void deserializeNBT(NBTTagCompound nbt) {
        if(nbt.hasKey("energy")) {
            final int tempEnergy = nbt.getInteger("energy");
            
            // prevent modifying stored energy
            if(tempEnergy >= this.capacity)
                this.energy = this.capacity;
            else if(tempEnergy < this.capacity)
                this.energy = tempEnergy;
            else this.energy = 0;
        } else if(nbt.hasKey("Energy")) {
        	// TODO: remove
        	// Fallback to support old alpha versions
            final int tempEnergy = nbt.getInteger("Energy");
            
            // prevent modifying stored energy
            if(tempEnergy >= this.capacity)
                this.energy = this.capacity;
            else if(tempEnergy < this.capacity)
                this.energy = tempEnergy;
            else this.energy = 0;
        }
        
        if(nbt.hasKey("capacity"))
        	this.capacity = nbt.getInteger("capacity");
        if(nbt.hasKey("extract"))
        	this.maxExtract = nbt.getInteger("extract");
        if(nbt.hasKey("receive"))
        	this.maxReceive = nbt.getInteger("receive");
    }

 

Note: Thats not all the code of the classes, but instead the most related code to the problem.

Developer of Primeval Forest.

Link to comment
Share on other sites

1 minute ago, Bektor said:

public TileEntityEnergy() { this.container = new EnergyManager(0, 0); }

 

1 minute ago, Bektor said:

if(tempEnergy >= this.capacity) this.energy = this.capacity;

 

You still have the same issue. Upon deserialization your EnergyManager from NBT you set the energy to 0 as the capacity is 0(as you initiate the container field to have 0 capacity in the parameterless constructor). Then after you are done setting the energy to capacity(which is 0) you read the capacity. The deserialization order is mixed.

  • Like 1
Link to comment
Share on other sites

3 minutes ago, V0idWa1k3r said:

 

 

You still have the same issue. Upon deserialization your EnergyManager from NBT you set the energy to 0 as the capacity is 0(as you initiate the container field to have 0 capacity in the parameterless constructor). Then after you are done setting the energy to capacity(which is 0) you read the capacity. The deserialization order is mixed.

Oh yeah, totally forgot the order of the deserialization method. ^^

Thx. Now it's working fine. :)

Developer of Primeval Forest.

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.