/* * 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.CompressedStreamTools; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.nbt.NBTTagList; import net.minecraft.tileentity.TileEntity; import net.minecraft.util.ResourceLocation; import net.minecraft.world.NextTickListEntry; import net.minecraft.world.WorldServer; import net.minecraft.world.chunk.NibbleArray; import net.minecraft.world.chunk.storage.ExtendedBlockStorage; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.List; import cubicchunks.CubicChunks; import cubicchunks.util.Coords; import cubicchunks.world.ServerHeightMap; import cubicchunks.world.column.Column; import cubicchunks.world.cube.Cube; import static cubicchunks.util.WorldServerAccess.getPendingTickListEntriesHashSet; import static cubicchunks.util.WorldServerAccess.getPendingTickListEntriesThisTick; class IONbtWriter { static byte[] writeNbtBytes(NBTTagCompound nbt) throws IOException { ByteArrayOutputStream buf = new ByteArrayOutputStream(); CompressedStreamTools.writeCompressed(nbt, buf); return buf.toByteArray(); } static NBTTagCompound write(Column column) { NBTTagCompound nbt = new NBTTagCompound(); writeBaseColumn(column, nbt); writeBiomes(column, nbt); writeOpacityIndex(column, nbt); return nbt; } static NBTTagCompound write(final Cube cube) { NBTTagCompound cubeNbt = new NBTTagCompound(); writeBaseCube(cube, cubeNbt); writeBlocks(cube, cubeNbt); writeEntities(cube, cubeNbt); writeTileEntities(cube, cubeNbt); writeScheduledTicks(cube, cubeNbt); writeLightingInfo(cube, cubeNbt); return cubeNbt; } private static void writeBaseColumn(Column column, NBTTagCompound nbt) {// coords nbt.setInteger("x", column.xPosition); nbt.setInteger("z", column.zPosition); // column properties nbt.setByte("v", (byte) 1); nbt.setLong("InhabitedTime", column.getInhabitedTime()); } private static void writeBiomes(Column column, NBTTagCompound nbt) {// biomes nbt.setByteArray("Biomes", column.getBiomeArray()); } private static void writeOpacityIndex(Column column, NBTTagCompound nbt) {// light index nbt.setByteArray("OpacityIndex", ((ServerHeightMap) column.getOpacityIndex()).getData()); } private static void writeBaseCube(Cube cube, NBTTagCompound cubeNbt) { cubeNbt.setByte("v", (byte) 1); // coords cubeNbt.setInteger("x", cube.getX()); cubeNbt.setInteger("y", cube.getY()); cubeNbt.setInteger("z", cube.getZ()); // save the worldgen stage and the target stage cubeNbt.setBoolean("populated", cube.isPopulated()); cubeNbt.setBoolean("fullyPopulated", cube.isFullyPopulated()); cubeNbt.setBoolean("initLightDone", cube.isInitialLightingDone()); } private static void writeBlocks(Cube cube, NBTTagCompound cubeNbt) { ExtendedBlockStorage ebs = cube.getStorage(); if (ebs == null) { return; // no data to save anyway } byte[] abyte = new byte[Cube.SIZE*Cube.SIZE*Cube.SIZE]; NibbleArray data = new NibbleArray(); NibbleArray add = ebs.getData().getDataForNBT(abyte, data); cubeNbt.setByteArray("Blocks", abyte); cubeNbt.setByteArray("Data", data.getData()); if (add != null) { cubeNbt.setByteArray("Add", add.getData()); } cubeNbt.setByteArray("BlockLight", ebs.getBlocklightArray().getData()); if (!cube.getCubicWorld().getProvider().getHasNoSky()) { cubeNbt.setByteArray("SkyLight", ebs.getSkylightArray().getData()); } } private static void writeEntities(Cube cube, NBTTagCompound cubeNbt) {// entities cube.getEntityContainer().writeToNbt(cubeNbt, "Entities", entity -> { // make sure this entity is really in the chunk int cubeX = Coords.getCubeXForEntity(entity); int cubeY = Coords.getCubeYForEntity(entity); int cubeZ = Coords.getCubeZForEntity(entity); if (cubeX != cube.getX() || cubeY != cube.getY() || cubeZ != cube.getZ()) { CubicChunks.LOGGER.warn(String.format("Saved entity %s in cube (%d,%d,%d) to cube (%d,%d,%d)! Entity thinks its in (%d,%d,%d)", entity.getClass().getName(), cubeX, cubeY, cubeZ, cube.getX(), cube.getY(), cube.getZ(), entity.chunkCoordX, entity.chunkCoordY, entity.chunkCoordZ )); } }); } private static void writeTileEntities(Cube cube, NBTTagCompound cubeNbt) {// tile entities NBTTagList nbtTileEntities = new NBTTagList(); cubeNbt.setTag("TileEntities", nbtTileEntities); for (TileEntity blockEntity : cube.getTileEntityMap().values()) { NBTTagCompound nbtTileEntity = new NBTTagCompound(); blockEntity.writeToNBT(nbtTileEntity); nbtTileEntities.appendTag(nbtTileEntity); } } private static void writeScheduledTicks(Cube cube, NBTTagCompound cubeNbt) {// scheduled block ticks Iterable<NextTickListEntry> scheduledTicks = getScheduledTicks(cube); if (scheduledTicks != null) { long time = cube.getCubicWorld().getTotalWorldTime(); NBTTagList nbtTicks = new NBTTagList(); cubeNbt.setTag("TileTicks", nbtTicks); for (NextTickListEntry scheduledTick : scheduledTicks) { NBTTagCompound nbtScheduledTick = new NBTTagCompound(); ResourceLocation resourcelocation = Block.REGISTRY.getNameForObject(scheduledTick.getBlock()); nbtScheduledTick.setString("i", resourcelocation.toString()); nbtScheduledTick.setInteger("x", scheduledTick.position.getX()); nbtScheduledTick.setInteger("y", scheduledTick.position.getY()); nbtScheduledTick.setInteger("z", scheduledTick.position.getZ()); nbtScheduledTick.setInteger("t", (int) (scheduledTick.scheduledTime - time)); nbtScheduledTick.setInteger("p", scheduledTick.priority); nbtTicks.appendTag(nbtScheduledTick); } } } private static void writeLightingInfo(Cube cube, NBTTagCompound cubeNbt) { NBTTagCompound lightingInfo = new NBTTagCompound(); cubeNbt.setTag("LightingInfo", lightingInfo); int[] lastHeightmap = cube.getColumn().getHeightMap(); lightingInfo.setIntArray("LastHeightMap", lastHeightmap); //TODO: why are we storing the height map on a Cube??? } private static List<NextTickListEntry> getScheduledTicks(Cube cube) { ArrayList<NextTickListEntry> out = new ArrayList<>(); // make sure this is a server if (!(cube.getCubicWorld() instanceof WorldServer)) { throw new Error("Column is not on the server!"); } WorldServer worldServer = (WorldServer) cube.getCubicWorld(); // copy the ticks for this cube copyScheduledTicks(out, getPendingTickListEntriesHashSet(worldServer), cube); copyScheduledTicks(out, getPendingTickListEntriesThisTick(worldServer), cube); return out; } private static void copyScheduledTicks(ArrayList<NextTickListEntry> out, Collection<NextTickListEntry> scheduledTicks, Cube cube) { for (NextTickListEntry scheduledTick : scheduledTicks) { if (cube.containsBlockPos(scheduledTick.position)) { out.add(scheduledTick); } } } }