/*
* This file is part of Cubic Chunks Mod, licensed under the MIT License (MIT).
*
* Copyright (c) 2015 contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package cubicchunks.server.chunkio;
import net.minecraft.block.Block;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.nbt.NBTTagList;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import net.minecraft.world.chunk.NibbleArray;
import net.minecraft.world.chunk.storage.ExtendedBlockStorage;
import net.minecraftforge.common.util.Constants;
import javax.annotation.Nullable;
import cubicchunks.CubicChunks;
import cubicchunks.lighting.LightingManager;
import cubicchunks.util.Coords;
import cubicchunks.world.ICubicWorld;
import cubicchunks.world.ICubicWorldServer;
import cubicchunks.world.ServerHeightMap;
import cubicchunks.world.column.Column;
import cubicchunks.world.cube.Cube;
public class IONbtReader {
@Nullable
static Column readColumn(ICubicWorld world, int x, int z, NBTTagCompound nbt) {
Column column = readBaseColumn(world, x, z, nbt);
if (column == null) {
return null;
}
readBiomes(nbt, column);
readOpacityIndex(nbt, column);
column.setModified(false); // its exactly the same as on disk so its not modified
return column;
}
@Nullable
private static Column readBaseColumn(ICubicWorld world, int x, int z, NBTTagCompound nbt) {// check the version number
byte version = nbt.getByte("v");
if (version != 1) {
throw new IllegalArgumentException(String.format("Column has wrong version: %d", version));
}
// check the coords
int xCheck = nbt.getInteger("x");
int zCheck = nbt.getInteger("z");
if (xCheck != x || zCheck != z) {
CubicChunks.LOGGER.warn(String.format("Column is corrupted! Expected (%d,%d) but got (%d,%d). Column will be regenerated.", x, z, xCheck, zCheck));
return null;
}
// create the column
Column column = new Column(world.getCubeCache(), world, x, z);
// read the rest of the column properties
column.setInhabitedTime(nbt.getLong("InhabitedTime"));
return column;
}
private static void readBiomes(NBTTagCompound nbt, Column column) {// biomes
column.setBiomeArray(nbt.getByteArray("Biomes"));
}
private static void readOpacityIndex(NBTTagCompound nbt, Column column) {// biomes
((ServerHeightMap) column.getOpacityIndex()).readData(nbt.getByteArray("OpacityIndex"));
}
@Nullable
static Cube readCubeAsyncPart(Column column, final int cubeX, final int cubeY, final int cubeZ, NBTTagCompound nbt) {
if (column.getX() != cubeX || column.getZ() != cubeZ) {
throw new IllegalArgumentException(String.format("Invalid column (%d, %d) for cube at (%d, %d, %d)",
column.getX(), column.getZ(), cubeX, cubeY, cubeZ));
}
ICubicWorldServer world = (ICubicWorldServer) column.getWorld();
Cube cube = readBaseCube(column, cubeX, cubeY, cubeZ, nbt, world);
readBlocks(nbt, world, cube);
return cube;
}
static void readCubeSyncPart(Cube cube, ICubicWorldServer world, NBTTagCompound nbt) {
readEntities(nbt, world, cube);
readTileEntities(nbt, world, cube);
readScheduledBlockTicks(nbt, world);
readLightingInfo(cube, nbt, world);
cube.markSaved(); // its exactly the same as on disk so its not modified
}
@Nullable
private static Cube readBaseCube(Column column, int cubeX, int cubeY, int cubeZ, NBTTagCompound nbt, ICubicWorldServer world) {// check the version number
byte version = nbt.getByte("v");
if (version != 1) {
throw new IllegalArgumentException("Cube has wrong version! " + version);
}
// check the coordinates
int xCheck = nbt.getInteger("x");
int yCheck = nbt.getInteger("y");
int zCheck = nbt.getInteger("z");
if (xCheck != cubeX || yCheck != cubeY || zCheck != cubeZ) {
CubicChunks.LOGGER.error(String.format("Cube is corrupted! Expected (%d,%d,%d) but got (%d,%d,%d). Cube will be regenerated.", cubeX, cubeY, cubeZ, xCheck, yCheck, zCheck));
return null;
}
// check against column
assert cubeX == column.xPosition && cubeZ == column.zPosition :
String.format("Cube is corrupted! Cube (%d,%d,%d) does not match column (%d,%d).", cubeX, cubeY, cubeZ, column.xPosition, column.zPosition);
// build the cube
final Cube cube = new Cube(column, cubeY);
// set the worldgen stage
cube.setPopulated(nbt.getBoolean("populated"));
cube.setFullyPopulated(nbt.getBoolean("fullyPopulated"));
cube.setInitialLightingDone(nbt.getBoolean("initLightDone"));
return cube;
}
private static void readBlocks(NBTTagCompound nbt, ICubicWorldServer world, Cube cube) {
boolean isEmpty = !nbt.hasKey("Blocks");// is this an empty cube?
if (!isEmpty) {
ExtendedBlockStorage ebs = new ExtendedBlockStorage(Coords.cubeToMinBlock(cube.getY()), !cube.getCubicWorld().getProvider().getHasNoSky());
byte[] abyte = nbt.getByteArray("Blocks");
NibbleArray data = new NibbleArray(nbt.getByteArray("Data"));
NibbleArray add = nbt.hasKey("Add", 7) ? new NibbleArray(nbt.getByteArray("Add")) : null;
ebs.getData().setDataFromNBT(abyte, data, add);
ebs.setBlocklightArray(new NibbleArray(nbt.getByteArray("BlockLight")));
if (!world.getProvider().getHasNoSky()) {
ebs.setSkylightArray(new NibbleArray(nbt.getByteArray("SkyLight")));
}
ebs.removeInvalidBlocks();
cube.setStorage(ebs);
}
}
private static void readEntities(NBTTagCompound nbt, ICubicWorldServer world, Cube cube) {// entities
cube.getEntityContainer().readFromNbt(nbt, "Entities", world, entity -> {
// make sure this entity is really in the chunk
int entityCubeX = Coords.getCubeXForEntity(entity);
int entityCubeY = Coords.getCubeYForEntity(entity);
int entityCubeZ = Coords.getCubeZForEntity(entity);
if (entityCubeX != cube.getX() || entityCubeY != cube.getY() || entityCubeZ != cube.getZ()) {
CubicChunks.LOGGER.warn(String.format("Loaded entity %s in cube (%d,%d,%d) to cube (%d,%d,%d)!", entity.getClass()
.getName(), entityCubeX, entityCubeY, entityCubeZ, cube.getX(), cube.getY(), cube.getZ()));
}
// The entity needs to know what Cube it is in, this is normally done in Cube.addEntity()
// but Cube.addEntity() is not used when loading entities
// (unlike vanilla which uses Chunk.addEntity() even when loading entities)
entity.addedToChunk = true;
entity.chunkCoordX = cube.getX();
entity.chunkCoordY = cube.getY();
entity.chunkCoordZ = cube.getZ();
});
}
private static void readTileEntities(NBTTagCompound nbt, ICubicWorldServer world, Cube cube) {// tile entities
NBTTagList nbtTileEntities = nbt.getTagList("TileEntities", Constants.NBT.TAG_COMPOUND);
if (nbtTileEntities == null) {
return;
}
for (int i = 0; i < nbtTileEntities.tagCount(); i++) {
NBTTagCompound nbtTileEntity = nbtTileEntities.getCompoundTagAt(i);
//TileEntity.create
TileEntity blockEntity = TileEntity.create((World) world, nbtTileEntity);
if (blockEntity != null) {
cube.addTileEntity(blockEntity);
}
}
}
private static void readScheduledBlockTicks(NBTTagCompound nbt, ICubicWorldServer world) {
NBTTagList nbtScheduledTicks = nbt.getTagList("TileTicks", 10);
if (nbtScheduledTicks == null) {
return;
}
for (int i = 0; i < nbtScheduledTicks.tagCount(); i++) {
NBTTagCompound nbtScheduledTick = nbtScheduledTicks.getCompoundTagAt(i);
Block block;
if (nbtScheduledTick.hasKey("i", Constants.NBT.TAG_STRING)) {
block = Block.getBlockFromName(nbtScheduledTick.getString("i"));
} else {
block = Block.getBlockById(nbtScheduledTick.getInteger("i"));
}
world.scheduleBlockUpdate(
new BlockPos(
nbtScheduledTick.getInteger("x"),
nbtScheduledTick.getInteger("y"),
nbtScheduledTick.getInteger("z")
),
block,
nbtScheduledTick.getInteger("t"),
nbtScheduledTick.getInteger("p")
);
}
}
private static void readLightingInfo(Cube cube, NBTTagCompound nbt, ICubicWorldServer world) {
NBTTagCompound lightingInfo = nbt.getCompoundTag("LightingInfo");
int[] lastHeightMap = lightingInfo.getIntArray("LastHeightMap"); // NO NO NO! TODO: Why is hightmap being stored in Cube's data?! kill it!
int[] currentHeightMap = cube.getColumn().getHeightMap();
// assume changes outside of this cube have no effect on this cube.
// In practice changes up to 15 blocks above can affect it,
// but it will be fixed by lighting update in other cube anyway
int minBlockY = Coords.cubeToMinBlock(cube.getY());
int maxBlockY = Coords.cubeToMaxBlock(cube.getY());
for (int i = 0; i < currentHeightMap.length; i++) {
int currentY = currentHeightMap[i];
int lastY = lastHeightMap[i];
//sort currentY and lastY
int minUpdateY = Math.min(currentY, lastY);
int maxUpdateY = Math.max(currentY, lastY);
boolean needLightUpdate = minUpdateY != maxUpdateY &&
//if max update Y is below minY - nothing to update
!(maxUpdateY < minBlockY) &&
//if min update Y is above maxY - nothing to update
!(minUpdateY > maxBlockY);
if (needLightUpdate) {
//clamp min/max update Y to be within current cube bounds
if (minUpdateY < minBlockY) {
minUpdateY = minBlockY;
}
if (maxUpdateY > maxBlockY) {
maxUpdateY = maxBlockY;
}
assert minUpdateY <= maxUpdateY : "minUpdateY > maxUpdateY: " + minUpdateY + ">" + maxUpdateY;
int localX = i & 0xF;
int localZ = i >> 4;
cube.getCubicWorld().getLightingManager().columnSkylightUpdate(
LightingManager.UpdateType.QUEUED, cube.getColumn(),
localX,
minUpdateY, maxUpdateY,
localZ
);
}
}
}
}