blob: 770982446b882f3519b094b4a7799e7554a08e44 [file] [log] [blame] [raw]
package net.glowstone.io.anvil;
import net.glowstone.GlowChunk;
import net.glowstone.GlowChunk.ChunkSection;
import net.glowstone.GlowChunkSnapshot;
import net.glowstone.GlowServer;
import net.glowstone.block.GlowBlockState;
import net.glowstone.io.ChunkIoService;
import net.glowstone.io.blockstate.BlockStateStore;
import net.glowstone.io.blockstate.BlockStateStoreLookupService;
import net.glowstone.util.nbt.*;
import org.bukkit.block.Biome;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* An implementation of the {@link ChunkIoService} which reads and writes Anvil maps,
* an improvement on the McRegion file format.
*/
public final class AnvilChunkIoService implements ChunkIoService {
/**
* The size of a region - a 32x32 group of chunks.
*/
private static final int REGION_SIZE = 32;
/**
* The root directory of the map.
*/
private final File dir;
/**
* The region file cache.
*/
private final RegionFileCache cache = new RegionFileCache(".mca");
// TODO: consider the session.lock file
public AnvilChunkIoService(File dir) {
this.dir = dir;
}
@SuppressWarnings("unchecked")
public boolean read(GlowChunk chunk, int x, int z) throws IOException {
RegionFile region = cache.getRegionFile(dir, x, z);
int regionX = x & (REGION_SIZE - 1);
int regionZ = z & (REGION_SIZE - 1);
if (!region.hasChunk(regionX, regionZ)) {
return false;
}
DataInputStream in = region.getChunkDataInputStream(regionX, regionZ);
NBTInputStream nbt = new NBTInputStream(in, false);
CompoundTag tag = (CompoundTag) nbt.readTag();
Map<String, Tag> levelTags = ((CompoundTag) tag.getValue().get("Level")).getValue();
nbt.close();
// read the vertical sections
List<CompoundTag> sectionList = ((ListTag<CompoundTag>) levelTags.get("Sections")).getValue();
ChunkSection[] sections = new ChunkSection[16];
for (CompoundTag sectionTag : sectionList) {
int y = ((ByteTag) sectionTag.getValue().get("Y")).getValue();
byte[] types = ((ByteArrayTag) sectionTag.getValue().get("Blocks")).getValue();
byte[] data = ((ByteArrayTag) sectionTag.getValue().get("Data")).getValue();
byte[] blockLight = ((ByteArrayTag) sectionTag.getValue().get("BlockLight")).getValue();
byte[] skyLight = ((ByteArrayTag) sectionTag.getValue().get("SkyLight")).getValue();
sections[y] = new ChunkSection(types, expand(data), expand(skyLight), expand(blockLight));
}
// initialize the chunk
chunk.initializeSections(sections);
chunk.setPopulated(((ByteTag) levelTags.get("TerrainPopulated")).getValue() == 1);
// read "Biomes" eventually
// read "Entities" eventually
// read "HeightMap" if we need to
// read tile entities
List<CompoundTag> storedTileEntities = ((ListTag<CompoundTag>) levelTags.get("TileEntities")).getValue();
for (CompoundTag tileEntityTag : storedTileEntities) {
GlowBlockState state = chunk.getBlock(((IntTag) tileEntityTag.getValue().get("x")).getValue(),
((IntTag) tileEntityTag.getValue().get("y")).getValue(), ((IntTag) tileEntityTag.getValue().get("z")).getValue()).getState();
if (state.getClass() != GlowBlockState.class) {
BlockStateStore store = BlockStateStoreLookupService.find(((StringTag) tileEntityTag.getValue().get("id")).getValue());
if (store != null) {
store.load(state, tileEntityTag);
} else {
GlowServer.logger.severe("Unable to find store for BlockState " + state.getClass());
}
}
}
return true;
}
/**
* Expand a half-length array into a full-length array.
*/
private byte[] expand(byte[] data) {
byte[] result = new byte[data.length * 2];
for (int i = 0; i < data.length; ++i) {
result[i * 2] = (byte) (data[i] & 0x0f);
result[i * 2 + 1] = (byte) ((data[i] & 0xf0) >> 4);
}
return result;
}
/**
* Shrink a full-length array into a half-length array.
*/
private byte[] shrink(byte[] data) {
byte[] result = new byte[data.length / 2];
for (int i = 0; i < data.length; i += 2) {
result[i / 2] = (byte) ((data[i + 1] << 4) | data[i]);
}
return result;
}
/**
* Writes a chunk. Currently not compatible with the vanilla server.
*
* @param x The X coordinate.
* @param z The Z coordinate.
* @param chunk The {@link net.glowstone.GlowChunk}.
* @throws java.io.IOException
*/
public void write(int x, int z, GlowChunk chunk) throws IOException {
RegionFile region = cache.getRegionFile(dir, x, z);
int regionX = x & (REGION_SIZE - 1);
int regionZ = z & (REGION_SIZE - 1);
DataOutputStream out = region.getChunkDataOutputStream(regionX, regionZ);
NBTOutputStream nbt = new NBTOutputStream(out, false);
Map<String, Tag> levelTags = new HashMap<String, Tag>();
// core properties
levelTags.put("xPos", new IntTag("xPos", chunk.getX()));
levelTags.put("zPos", new IntTag("zPos", chunk.getZ()));
levelTags.put("TerrainPopulated", new ByteTag("TerrainPopulated", (byte) (chunk.isPopulated() ? 1 : 0)));
levelTags.put("LastUpdate", new LongTag("LastUpdate", 0));
// chunk sections
List<CompoundTag> sectionTags = new ArrayList<CompoundTag>();
GlowChunkSnapshot snapshot = chunk.getChunkSnapshot(true, true, false);
ChunkSection[] sections = snapshot.getRawSections();
for (byte i = 0; i < sections.length; ++i) {
ChunkSection sec = sections[i];
if (sec == null) continue;
Map<String, Tag> map = new HashMap<String, Tag>();
map.put("Y", new ByteTag("Y", i));
map.put("Blocks", new ByteArrayTag("Blocks", sec.types));
map.put("Data", new ByteArrayTag("Data", shrink(sec.metaData)));
map.put("BlockLight", new ByteArrayTag("BlockLight", shrink(sec.blockLight)));
map.put("SkyLight", new ByteArrayTag("SkyLight", shrink(sec.skyLight)));
sectionTags.add(new CompoundTag("", map));
}
levelTags.put("Sections", new ListTag<CompoundTag>("Sections", CompoundTag.class, sectionTags));
// height map
levelTags.put("HeightMap", new IntArrayTag("HeightMap", snapshot.getRawHeightmap()));
// biomes
Biome[] biomesArray = snapshot.getRawBiomes();
byte[] biomes = new byte[biomesArray.length];
for (int i = 0; i < biomes.length; ++i) {
biomes[i] = 0; // todo: convert Biome to value
}
levelTags.put("Biomes", new ByteArrayTag("Biomes", biomes));
// todo: entities
List<CompoundTag> entities = new ArrayList<CompoundTag>();
/* for (Entity entity : chunk.getEntities()) {
GlowEntity glowEntity = (GlowEntity) entity;
EntityStore store = EntityStoreLookupService.find(glowEntity.getClass());
if (store == null)
continue;
entities.add(new CompoundTag("", store.save(glowEntity)));
} */
levelTags.put("Entities", new ListTag<CompoundTag>("Entities", CompoundTag.class, entities));
// tile entities
List<CompoundTag> tileEntities = new ArrayList<CompoundTag>();
for (GlowBlockState state : chunk.getTileEntities()) {
if (state.getClass() != GlowBlockState.class) {
BlockStateStore store = BlockStateStoreLookupService.find(state.getClass());
if (store != null) {
tileEntities.add(new CompoundTag("", store.save(state)));
} else {
GlowServer.logger.severe("Unable to find store for BlockState " + state.getClass());
}
}
}
levelTags.put("TileEntities", new ListTag<CompoundTag>("TileEntities", CompoundTag.class, tileEntities));
Map<String, Tag> levelOut = new HashMap<String, Tag>();
levelOut.put("Level", new CompoundTag("Level", levelTags));
nbt.writeTag(new CompoundTag("", levelOut));
nbt.close();
}
public void unload() throws IOException {
cache.clear();
}
}