Java tutorial
/* * This file is part of Spout. * * Copyright (c) 2011-2012, SpoutDev <http://www.spout.org/> * Spout is licensed under the SpoutDev License Version 1. * * Spout is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * In addition, 180 days after any changes are published, you can use the * software, incorporating those changes, under the terms of the MIT license, * as described in the SpoutDev License Version 1. * * Spout is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License, * the MIT license and the SpoutDev License Version 1 along with this program. * If not, see <http://www.gnu.org/licenses/> for the GNU Lesser General Public * License and see <http://www.spout.org/SpoutDevLicenseV1.txt> for the full license, * including the MIT license. */ package org.spout.engine.filesystem; import gnu.trove.procedure.TShortObjectProcedure; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Random; import java.util.UUID; import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Level; import org.apache.commons.io.FileUtils; import org.spout.api.Spout; import org.spout.api.component.Component; import org.spout.api.component.components.BlockComponent; import org.spout.api.datatable.ManagedHashMap; import org.spout.api.entity.EntitySnapshot; import org.spout.api.entity.PlayerSnapshot; import org.spout.api.generator.biome.BiomeManager; import org.spout.api.geo.LoadOption; import org.spout.api.geo.World; import org.spout.api.geo.cuboid.ChunkSnapshot.BlockComponentSnapshot; import org.spout.api.geo.cuboid.Region; import org.spout.api.geo.discrete.Point; import org.spout.api.geo.discrete.Transform; import org.spout.api.material.BlockMaterial; import org.spout.api.material.ComplexMaterial; import org.spout.api.material.Material; import org.spout.api.material.MaterialRegistry; import org.spout.api.material.block.BlockFullState; import org.spout.api.math.Quaternion; import org.spout.api.math.Vector3; import org.spout.api.plugin.CommonClassLoader; import org.spout.api.util.NBTMapper; import org.spout.api.util.StringMap; import org.spout.api.util.hashing.ByteTripleHashed; import org.spout.api.util.hashing.NibbleQuadHashed; import org.spout.api.util.hashing.SignedTenBitTripleHashed; import org.spout.api.util.sanitation.SafeCast; import org.spout.api.util.typechecker.TypeChecker; import org.spout.engine.SpoutEngine; import org.spout.engine.entity.SpoutEntity; import org.spout.engine.entity.SpoutPlayer; import org.spout.engine.world.SpoutChunk; import org.spout.engine.world.SpoutChunk.PopulationState; import org.spout.engine.world.SpoutChunkSnapshot; import org.spout.engine.world.SpoutColumn; import org.spout.engine.world.SpoutRegion; import org.spout.engine.world.SpoutWorld; import org.spout.engine.world.dynamic.DynamicBlockUpdate; import org.spout.nbt.ByteArrayTag; import org.spout.nbt.ByteTag; import org.spout.nbt.CompoundMap; import org.spout.nbt.CompoundTag; import org.spout.nbt.FloatTag; import org.spout.nbt.IntArrayTag; import org.spout.nbt.IntTag; import org.spout.nbt.ListTag; import org.spout.nbt.LongTag; import org.spout.nbt.ShortTag; import org.spout.nbt.StringTag; import org.spout.nbt.Tag; import org.spout.nbt.stream.NBTInputStream; import org.spout.nbt.stream.NBTOutputStream; public class WorldFiles { private static final TypeChecker<List<? extends CompoundTag>> checkerListCompoundTag = TypeChecker .tList(CompoundTag.class); public static final byte WORLD_VERSION = 2; public static final byte ENTITY_VERSION = 1; public static final byte CHUNK_VERSION = 1; public static final int COLUMN_VERSION = 1; public static void savePlayerData(List<SpoutPlayer> Players) { for (SpoutPlayer player : Players) { savePlayerData(player); } } public static boolean savePlayerData(SpoutPlayer player) { File playerDir = new File(Spout.getEngine().getDataFolder().toString(), "players"); //Save data to temp file first String fileName = player.getName() + ".dat"; String tempName = fileName + ".temp"; File playerData = new File(playerDir, tempName); if (!playerData.exists()) { try { playerData.createNewFile(); } catch (Exception e) { Spout.getLogger().log(Level.SEVERE, "Error creating player data for " + player.getName(), e); } } PlayerSnapshot snapshot = new PlayerSnapshot(player); CompoundTag playerTag = saveEntity(snapshot); NBTOutputStream os = null; try { os = new NBTOutputStream(new DataOutputStream(new FileOutputStream(playerData)), false); os.writeTag(playerTag); } catch (IOException e) { Spout.getLogger().log(Level.SEVERE, "Error saving player data for " + player.getName(), e); playerData.delete(); return false; } finally { if (os != null) { try { os.close(); } catch (IOException ignore) { } } } try { //Move the temp data to final location File finalData = new File(playerDir, fileName); if (finalData.exists()) { finalData.delete(); } FileUtils.moveFile(playerData, finalData); return true; } catch (IOException e) { Spout.getLogger().log(Level.SEVERE, "Error saving player data for " + player.getName(), e); playerData.delete(); return false; } } /** * Loads player data for the player, if it exists * * Returns null on failure or if the data could not be loaded. * If an exception is thrown or the player data is not in a valid format * it will be backed up and new player data will be created for the player * @param name * @return player, or null if it could not be loaded */ public static SpoutPlayer loadPlayerData(String name) { File playerDir = new File(Spout.getEngine().getDataFolder().toString(), "players"); String fileName = name + ".dat"; File playerData = new File(playerDir, fileName); if (playerData.exists()) { NBTInputStream is = null; try { is = new NBTInputStream(new DataInputStream(new FileInputStream(playerData)), false); CompoundTag dataTag = (CompoundTag) is.readTag(); World world = Spout.getEngine().getWorld(dataTag.getName()); return (SpoutPlayer) loadEntity(world, dataTag, name); } catch (Exception e) { Spout.getLogger().log(Level.SEVERE, "Error loading player data for " + name, e); //Back up the corrupt data, so new data can be saved //Back up the file with a unique name, based off the current system time SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String time = formatter.format(new Date(System.currentTimeMillis())); File backup = new File(playerDir, fileName + "_" + time + ".bak"); if (!playerData.renameTo(backup)) { Spout.getLogger().log(Level.SEVERE, "Failed to back up corrupt player data " + name); } else { Spout.getLogger().log(Level.WARNING, "Successfully backed up corrupt player data for " + name); } } finally { try { is.close(); } catch (IOException ignore) { } } } return null; } public static void saveChunk(SpoutWorld world, SpoutChunkSnapshot snapshot, List<DynamicBlockUpdate> blockUpdates, OutputStream dos) { CompoundMap chunkTags = new CompoundMap(); //Switch block ids from engine material ids to world specific ids StringMap global = ((SpoutEngine) Spout.getEngine()).getEngineItemMap(); StringMap itemMap = world.getItemMap(); int[] palette = snapshot.getPalette(); int[] packetBlockArray = snapshot.getPackedBlockArray(); int packedWidth = snapshot.getPackedWidth(); if (palette.length > 0) { convertArray(palette, global, itemMap); } else { convertArray(packetBlockArray, global, itemMap); } chunkTags.put(new ByteTag("version", CHUNK_VERSION)); chunkTags.put(new ByteTag("format", (byte) 0)); chunkTags.put(new IntTag("x", snapshot.getX())); chunkTags.put(new IntTag("y", snapshot.getY())); chunkTags.put(new IntTag("z", snapshot.getZ())); chunkTags.put(new ByteTag("populationState", snapshot.getPopulationState().getId())); chunkTags.put(new ByteTag("lightStable", snapshot.isLightStable())); chunkTags.put(new IntArrayTag("palette", palette)); chunkTags.put(new IntTag("packedWidth", packedWidth)); chunkTags.put(new IntArrayTag("packedBlockArray", packetBlockArray)); chunkTags.put(new ByteArrayTag("skyLight", snapshot.getSkyLight())); chunkTags.put(new ByteArrayTag("blockLight", snapshot.getBlockLight())); chunkTags.put(new CompoundTag("entities", saveEntities(snapshot.getEntities()))); chunkTags.put(saveDynamicUpdates(blockUpdates)); chunkTags.put(saveBlockComponents(snapshot.getBlockComponents())); chunkTags.put(new ByteArrayTag("extraData", snapshot.getDataMap().serialize())); CompoundTag chunkCompound = new CompoundTag("chunk", chunkTags); NBTOutputStream os = null; try { os = new NBTOutputStream(dos, false); os.writeTag(chunkCompound); } catch (IOException e) { Spout.getLogger().log(Level.SEVERE, "Error saving chunk {" + snapshot.getX() + ", " + snapshot.getY() + ", " + snapshot + "}", e); } world.getItemMap().save(); } public static SpoutChunk loadChunk(SpoutRegion r, int x, int y, int z, InputStream dis, ChunkDataForRegion dataForRegion) { SpoutChunk chunk = null; NBTInputStream is = null; try { if (dis == null) { //The inputstream is null because no chunk data exists return chunk; } is = new NBTInputStream(dis, false); CompoundTag chunkTag = (CompoundTag) is.readTag(); CompoundMap map = chunkTag.getValue(); int cx = r.getChunkX() + x; int cy = r.getChunkY() + y; int cz = r.getChunkZ() + z; //Convert world block ids to engine material ids SpoutWorld world = r.getWorld(); StringMap global = ((SpoutEngine) Spout.getEngine()).getEngineItemMap(); StringMap itemMap = world.getItemMap(); byte[] skyLight = SafeCast.toByteArray(NBTMapper.toTagValue(map.get("skyLight")), null); byte[] blockLight = SafeCast.toByteArray(NBTMapper.toTagValue(map.get("blockLight")), null); byte[] extraData = SafeCast.toByteArray(NBTMapper.toTagValue(map.get("extraData")), null); ManagedHashMap extraDataMap = new ManagedHashMap(); extraDataMap.deserialize(extraData); boolean skipScan = false; byte populationState = SafeCast.toGeneric(map.get("populationState"), new ByteTag("", PopulationState.POPULATED.getId()), ByteTag.class).getValue(); boolean lightStable = SafeCast.toByte(NBTMapper.toTagValue(map.get("lightStable")), (byte) 0) != 0; int[] palette = SafeCast.toIntArray(NBTMapper.toTagValue(map.get("palette")), null); if (palette == null) { short[] blocks = SafeCast.toShortArray(NBTMapper.toTagValue(map.get("blocks")), null); short[] data = SafeCast.toShortArray(NBTMapper.toTagValue(map.get("data")), null); for (int i = 0; i < blocks.length; i++) { blocks[i] = (short) itemMap.convertTo(global, blocks[i]); } chunk = new SpoutChunk(r.getWorld(), r, cx, cy, cz, PopulationState.byID(populationState), blocks, data, skyLight, blockLight, extraDataMap, lightStable); } else { int blockArrayWidth = SafeCast.toInt(NBTMapper.toTagValue(map.get("packedWidth")), -1); int[] variableWidthBlockArray = SafeCast .toIntArray(NBTMapper.toTagValue(map.get("packedBlockArray")), null); if (palette.length > 0) { convertArray(palette, itemMap, global); skipScan = componentSkipCheck(palette); } else { convertArray(variableWidthBlockArray, itemMap, global); skipScan = componentSkipCheck(variableWidthBlockArray); } chunk = new SpoutChunk(r.getWorld(), r, cx, cy, cz, PopulationState.byID(populationState), palette, blockArrayWidth, variableWidthBlockArray, skyLight, blockLight, extraDataMap, lightStable); } CompoundMap entityMap = SafeCast.toGeneric(NBTMapper.toTagValue(map.get("entities")), (CompoundMap) null, CompoundMap.class); loadEntities(r, entityMap, dataForRegion.loadedEntities); List<? extends CompoundTag> updateList = checkerListCompoundTag.checkTag(map.get("dynamic_updates")); loadDynamicUpdates(updateList, dataForRegion.loadedUpdates); List<? extends CompoundTag> componentsList = checkerListCompoundTag .checkTag(map.get("block_components"), null); //Load Block components //This is a three-part process //1.) Scan the blocks and add them to the chunk map //2.) Load the datatables associated with the block components //3.) Attach the components if (!skipScan) { chunk.blockComponentScan(); } //Load data associated with block components loadBlockComponents(chunk, componentsList); //Attach block components chunk.getBlockComponents().forEachEntry(new AttachComponentProcedure()); } catch (IOException e) { e.printStackTrace(); } finally { if (is != null) { try { is.close(); } catch (IOException ignore) { } } } return chunk; } private static void convertArray(int[] fullState, StringMap from, StringMap to) { for (int i = 0; i < fullState.length; i++) { short newId = (short) from.convertTo(to, BlockFullState.getId(fullState[i])); short oldData = BlockFullState.getData(fullState[i]); fullState[i] = BlockFullState.getPacked(newId, oldData); } } private static boolean componentSkipCheck(int[] fullState) { for (int i = 0; i < fullState.length; i++) { if (BlockFullState.getMaterial(fullState[i]) instanceof ComplexMaterial) { return false; } } return true; } private static class AttachComponentProcedure implements TShortObjectProcedure<BlockComponent> { @Override public boolean execute(short a, BlockComponent b) { try { b.onAttached(); } catch (Exception e) { Spout.getLogger().log(Level.SEVERE, "Unhandled exception attaching block component", e); } return true; } } @SuppressWarnings("rawtypes") private static void loadEntities(SpoutRegion r, CompoundMap map, List<SpoutEntity> loadedEntities) { if (r != null && map != null) { for (Tag tag : map) { SpoutEntity e = loadEntity(r, (CompoundTag) tag); if (e != null) { loadedEntities.add(e); } } } } @SuppressWarnings("rawtypes") private static CompoundMap saveEntities(List<EntitySnapshot> entities) { CompoundMap tagMap = new CompoundMap(); for (EntitySnapshot e : entities) { //Players are saved elsewhere if (!(e instanceof PlayerSnapshot)) { Tag tag = saveEntity(e); if (tag != null) { tagMap.put(tag); } } } return tagMap; } private static SpoutEntity loadEntity(SpoutRegion r, CompoundTag tag) { return loadEntity(r.getWorld(), tag, null); } private static SpoutEntity loadEntity(World w, CompoundTag tag, String name) { try { return loadEntityImpl(w, tag, name); } catch (Exception e) { Spout.getLogger().log(Level.SEVERE, "Unable to load entity", e); } return null; } @SuppressWarnings("unchecked") private static SpoutEntity loadEntityImpl(World w, CompoundTag tag, String name) { CompoundMap map = tag.getValue(); @SuppressWarnings("unused") byte version = SafeCast.toByte(NBTMapper.toTagValue(map.get("version")), (byte) 0); boolean player = SafeCast.toByte(NBTMapper.toTagValue(map.get("player")), (byte) 0) == 1; //Read entity Float pX = SafeCast.toFloat(NBTMapper.toTagValue(map.get("posX")), Float.MAX_VALUE); Float pY = SafeCast.toFloat(NBTMapper.toTagValue(map.get("posY")), Float.MAX_VALUE); Float pZ = SafeCast.toFloat(NBTMapper.toTagValue(map.get("posZ")), Float.MAX_VALUE); if (pX == Float.MAX_VALUE || pY == Float.MAX_VALUE || pZ == Float.MAX_VALUE) { return null; } float sX = SafeCast.toFloat(NBTMapper.toTagValue(map.get("scaleX")), 1.0F); float sY = SafeCast.toFloat(NBTMapper.toTagValue(map.get("scaleY")), 1.0F); float sZ = SafeCast.toFloat(NBTMapper.toTagValue(map.get("scaleZ")), 1.0F); float qX = SafeCast.toFloat(NBTMapper.toTagValue(map.get("quatX")), 0.0F); float qY = SafeCast.toFloat(NBTMapper.toTagValue(map.get("quatY")), 0.0F); float qZ = SafeCast.toFloat(NBTMapper.toTagValue(map.get("quatZ")), 0.0F); float qW = SafeCast.toFloat(NBTMapper.toTagValue(map.get("quatW")), 1.0F); long msb = SafeCast.toLong(NBTMapper.toTagValue(map.get("UUID_msb")), new Random().nextLong()); long lsb = SafeCast.toLong(NBTMapper.toTagValue(map.get("UUID_lsb")), new Random().nextLong()); UUID uid = new UUID(msb, lsb); int view = SafeCast.toInt(NBTMapper.toTagValue(map.get("view")), 0); boolean observer = SafeCast .toGeneric(NBTMapper.toTagValue(map.get("observer")), new ByteTag("", (byte) 0), ByteTag.class) .getBooleanValue(); //Setup data boolean controllerDataExists = SafeCast.toGeneric(NBTMapper.toTagValue(map.get("controller_data_exists")), new ByteTag("", (byte) 0), ByteTag.class).getBooleanValue(); byte[] dataMap = null; if (controllerDataExists) { dataMap = SafeCast.toByteArray(NBTMapper.toTagValue(map.get("controller_data")), new byte[0]); } //Setup entity Region r = w.getRegionFromBlock(Math.round(pX), Math.round(pY), Math.round(pZ), player ? LoadOption.LOAD_GEN : LoadOption.NO_LOAD); if (r == null) { // TODO - this should never happen - entities should be located in the chunk that was just loaded Spout.getLogger().info("Attempted to load entity to unloaded region"); Thread.dumpStack(); return null; } final Transform t = new Transform(new Point(r.getWorld(), pX, pY, pZ), new Quaternion(qX, qY, qZ, qW, false), new Vector3(sX, sY, sZ)); ListTag<StringTag> components = (ListTag<StringTag>) map.get("components"); List<Class<? extends Component>> types = new ArrayList<Class<? extends Component>>( components.getValue().size()); for (StringTag component : components.getValue()) { try { try { Class<? extends Component> clazz = (Class<? extends Component>) CommonClassLoader .findPluginClass(component.getValue()); types.add(clazz); } catch (ClassNotFoundException e) { Class<? extends Component> clazz = (Class<? extends Component>) Class .forName(component.getValue()); types.add(clazz); } } catch (ClassNotFoundException e) { Spout.getLogger().log(Level.SEVERE, "Unable to find component class " + component.getValue(), e); } } SpoutEntity e; if (!player) { e = new SpoutEntity(t, view, uid, false, dataMap, types.toArray(new Class[types.size()])); e.setObserver(observer); } else { e = new SpoutPlayer(name, t, view, uid, false, dataMap, types.toArray(new Class[types.size()])); } return e; } private static CompoundTag saveEntity(EntitySnapshot e) { if (!e.isSavable() && (!(e instanceof PlayerSnapshot))) { return null; } CompoundMap map = new CompoundMap(); map.put(new ByteTag("version", ENTITY_VERSION)); map.put(new ByteTag("player", (e instanceof PlayerSnapshot))); //Write entity Transform t = e.getTransform(); map.put(new FloatTag("posX", t.getPosition().getX())); map.put(new FloatTag("posY", t.getPosition().getY())); map.put(new FloatTag("posZ", t.getPosition().getZ())); map.put(new FloatTag("scaleX", t.getScale().getX())); map.put(new FloatTag("scaleY", t.getScale().getY())); map.put(new FloatTag("scaleZ", t.getScale().getZ())); map.put(new FloatTag("quatX", t.getRotation().getX())); map.put(new FloatTag("quatY", t.getRotation().getY())); map.put(new FloatTag("quatZ", t.getRotation().getZ())); map.put(new FloatTag("quatW", t.getRotation().getW())); map.put(new LongTag("UUID_msb", e.getUID().getMostSignificantBits())); map.put(new LongTag("UUID_lsb", e.getUID().getLeastSignificantBits())); map.put(new IntTag("view", e.getViewDistance())); map.put(new ByteTag("observer", e.isObserver())); //Serialize data if (!e.getDataMap().isEmpty()) { map.put(new ByteTag("controller_data_exists", true)); map.put(new ByteArrayTag("controller_data", e.getDataMap().serialize())); } else { map.put(new ByteTag("controller_data_exists", false)); } List<StringTag> components = new ArrayList<StringTag>(); for (Class<? extends Component> clazz : e.getComponents()) { components.add(new StringTag("component", clazz.getName())); } map.put(new ListTag<StringTag>("components", StringTag.class, components)); CompoundTag tag = null; if (e instanceof PlayerSnapshot) { tag = new CompoundTag(e.getWorldName(), map); } else { tag = new CompoundTag("entity_" + e.getId(), map); } return tag; } private static ListTag<CompoundTag> saveBlockComponents(List<BlockComponentSnapshot> components) { List<CompoundTag> list = new ArrayList<CompoundTag>(components.size()); for (BlockComponentSnapshot snapshot : components) { CompoundTag tag = saveBlockComponent(snapshot); if (tag != null) { list.add(tag); } } return new ListTag<CompoundTag>("block_components", CompoundTag.class, list); } private static CompoundTag saveBlockComponent(BlockComponentSnapshot snapshot) { if (!snapshot.getData().isEmpty()) { byte[] data = snapshot.getData().serialize(); if (data != null && data.length > 0) { CompoundMap map = new CompoundMap(); short packed = NibbleQuadHashed.key(snapshot.getX(), snapshot.getY(), snapshot.getZ(), 0); map.put(new ShortTag("packed", packed)); map.put(new ByteArrayTag("data", data)); return new CompoundTag("block_component_" + packed, map); } } return null; } private static ListTag<CompoundTag> saveDynamicUpdates(List<DynamicBlockUpdate> updates) { List<CompoundTag> list = new ArrayList<CompoundTag>(updates.size()); for (DynamicBlockUpdate update : updates) { CompoundTag tag = saveDynamicUpdate(update); if (tag != null) { list.add(tag); } } return new ListTag<CompoundTag>("dynamic_updates", CompoundTag.class, list); } private static void loadBlockComponents(SpoutChunk chunk, List<? extends CompoundTag> list) { if (list == null) { return; } for (CompoundTag compoundTag : list) { CompoundMap map = compoundTag.getValue(); short packed = (Short) map.get("packed").getValue(); ByteArrayTag data = (ByteArrayTag) map.get("data"); BlockComponent component = chunk.getBlockComponents().get(packed); if (component != null) { try { component.getOwner().getData().deserialize(data.getValue()); } catch (IOException e) { Spout.getLogger().log(Level.SEVERE, "Unhandled exception deserializing block component data", e); } } } } private static CompoundTag saveDynamicUpdate(DynamicBlockUpdate update) { CompoundMap map = new CompoundMap(); map.put(new IntTag("packedv2", update.getPacked())); map.put(new LongTag("nextUpdate", update.getNextUpdate())); map.put(new IntTag("data", update.getData())); return new CompoundTag("update", map); } private static void loadDynamicUpdates(List<? extends CompoundTag> list, List<DynamicBlockUpdate> loadedUpdates) { if (list == null) { return; } for (CompoundTag compoundTag : list) { DynamicBlockUpdate update = loadDynamicUpdate(compoundTag); if (update == null) { continue; } loadedUpdates.add(update); } } private static DynamicBlockUpdate loadDynamicUpdate(CompoundTag compoundTag) { final CompoundMap map = compoundTag.getValue(); int packed = SafeCast.toInt(NBTMapper.toTagValue(map.get("packedv2")), -1); if (packed == -1) { packed = SafeCast.toInt(NBTMapper.toTagValue(map.get("packed")), -1); if (packed < 0) { return null; } else { int x = 0xFF & ByteTripleHashed.key1(packed); int y = 0xFF & ByteTripleHashed.key2(packed); int z = 0xFF & ByteTripleHashed.key3(packed); packed = SignedTenBitTripleHashed.key(x, y, z); } } final long nextUpdate = SafeCast.toLong(NBTMapper.toTagValue(map.get("nextUpdate")), -1L); if (nextUpdate < 0) { return null; } final int data = SafeCast.toInt(NBTMapper.toTagValue(map.get("data")), 0); return new DynamicBlockUpdate(packed, nextUpdate, data); } public static void readColumn(InputStream in, SpoutColumn column, AtomicInteger lowestY, BlockMaterial[][] topmostBlocks) { if (in == null) { //The inputstream is null because no height map data exists for (int x = 0; x < SpoutColumn.BLOCKS.SIZE; x++) { for (int z = 0; z < SpoutColumn.BLOCKS.SIZE; z++) { column.getAtomicInteger(x, z).set(Integer.MIN_VALUE); topmostBlocks[x][z] = null; column.setDirty(x, z); } } lowestY.set(Integer.MAX_VALUE); return; } DataInputStream dataStream = new DataInputStream(in); try { for (int x = 0; x < SpoutColumn.BLOCKS.SIZE; x++) { for (int z = 0; z < SpoutColumn.BLOCKS.SIZE; z++) { column.getAtomicInteger(x, z).set(dataStream.readInt()); } } @SuppressWarnings("unused") int version = dataStream.readInt(); lowestY.set(dataStream.readInt()); //Save heightmap StringMap global = ((SpoutEngine) Spout.getEngine()).getEngineItemMap(); StringMap itemMap = column.getWorld().getItemMap(); boolean warning = false; for (int x = 0; x < SpoutColumn.BLOCKS.SIZE; x++) { for (int z = 0; z < SpoutColumn.BLOCKS.SIZE; z++) { if (!dataStream.readBoolean()) { continue; } int blockState = dataStream.readInt(); short blockId = BlockFullState.getId(blockState); short blockData = BlockFullState.getData(blockState); blockId = (short) itemMap.convertTo(global, blockId); blockState = BlockFullState.getPacked(blockId, blockData); BlockMaterial m; try { m = (BlockMaterial) MaterialRegistry.get(blockState); } catch (ClassCastException e) { m = null; if (!warning) { Spout.getLogger().severe( "Error reading column topmost block information, block was not a valid BlockMaterial"); warning = false; } } if (m == null) { column.setDirty(x, z); } topmostBlocks[x][z] = m; } } //Save Biomes BiomeManager manager = null; try { //Biome manager is serialized with: // - boolean, if a biome manager exists // - String, the class name // - int, the number of bytes of data to read // - byte[], size of the above int in length boolean exists = dataStream.readBoolean(); if (exists) { String biomeManagerClass = dataStream.readUTF(); int biomeSize = dataStream.readInt(); byte[] biomes = new byte[biomeSize]; dataStream.readFully(biomes); //Attempt to create the biome manager class from the class name @SuppressWarnings("unchecked") Class<? extends BiomeManager> clazz = (Class<? extends BiomeManager>) Class .forName(biomeManagerClass); Class<?>[] params = { int.class, int.class }; manager = clazz.getConstructor(params).newInstance(column.getX(), column.getZ()); manager.deserialize(biomes); column.setBiomeManager(manager); } } catch (Exception e) { Spout.getLogger().log(Level.SEVERE, "Failed to read biome data for column", e); } } catch (IOException e) { Spout.getLogger() .severe("Error reading column height-map for column" + column.getX() + ", " + column.getZ()); } } public static void writeColumn(OutputStream out, SpoutColumn column, AtomicInteger lowestY, BlockMaterial[][] topmostBlocks) { DataOutputStream dataStream = new DataOutputStream(out); try { for (int x = 0; x < SpoutColumn.BLOCKS.SIZE; x++) { for (int z = 0; z < SpoutColumn.BLOCKS.SIZE; z++) { dataStream.writeInt(column.getAtomicInteger(x, z).get()); } } dataStream.writeInt(COLUMN_VERSION); dataStream.writeInt(lowestY.get()); StringMap global = ((SpoutEngine) Spout.getEngine()).getEngineItemMap(); StringMap itemMap; itemMap = column.getWorld().getItemMap(); for (int x = 0; x < SpoutColumn.BLOCKS.SIZE; x++) { for (int z = 0; z < SpoutColumn.BLOCKS.SIZE; z++) { Material m = topmostBlocks[x][z]; if (m == null) { dataStream.writeBoolean(false); continue; } else { dataStream.writeBoolean(true); } short blockId = m.getId(); short blockData = m.getData(); blockId = (short) global.convertTo(itemMap, blockId); dataStream.writeInt(BlockFullState.getPacked(blockId, blockData)); } } //Biome manager is serialized with: // - boolean, if a biome manager exists // - String, the class name // - int, the number of bytes of data to read // - byte[], size of the above int in length BiomeManager manager = column.getBiomeManager(); if (manager != null) { dataStream.writeBoolean(true); dataStream.writeUTF(manager.getClass().getName()); byte[] data = manager.serialize(); dataStream.writeInt(data.length); dataStream.write(data); } else { dataStream.writeBoolean(false); } dataStream.flush(); } catch (IOException e) { Spout.getLogger() .severe("Error writing column height-map for column" + column.getX() + ", " + column.getZ()); } } }