/* * To change this template, choose Tools | Templates * and open the template in the editor. */ package org.pepsoft.worldpainter.exporting; import org.jnbt.CompoundTag; import org.jnbt.NBTInputStream; import org.jnbt.NBTOutputStream; import org.pepsoft.minecraft.*; import javax.vecmath.Point3i; import java.awt.*; import java.io.DataInputStream; import java.io.File; import java.io.IOException; import java.util.*; import static org.pepsoft.minecraft.Block.BLOCK_TYPE_NAMES; import static org.pepsoft.minecraft.Constants.BLK_AIR; import static org.pepsoft.minecraft.Constants.SUPPORTED_VERSION_2; /** * * @author pepijn */ public class WorldRegion implements MinecraftWorld { public WorldRegion(int regionX, int regionZ, int maxHeight, int version) { this.regionX = regionX; this.regionZ = regionZ; this.maxHeight = maxHeight; this.version = version; } public WorldRegion(File regionDir, int regionX, int regionZ, int maxHeight, int version) throws IOException { this.regionX = regionX; this.regionZ = regionZ; this.maxHeight = maxHeight; this.version = version; int lowestX = (regionX << 5) - 1; int highestX = lowestX + 33; int lowestZ = (regionZ << 5) - 1; int highestZ = lowestZ + 33; Map<Point, RegionFile> regionFiles = new HashMap<>(); // synchronized (DISK_ACCESS_MONITOR) { try { for (int x = lowestX; x <= highestX; x++) { for (int z = lowestZ; z <= highestZ; z++) { Point regionCoords = new Point(x >> 5, z >> 5); RegionFile regionFile = regionFiles.get(regionCoords); if (regionFile == null) { File file = new File(regionDir, "r." + regionCoords.x + "." + regionCoords.y + ((version == SUPPORTED_VERSION_2) ? ".mca" : ".mcr")); if (file.isFile()) { regionFile = new RegionFile(file); regionFiles.put(regionCoords, regionFile); } } if (regionFile != null) { DataInputStream chunkIn = regionFile.getChunkDataInputStream((x - (regionX << 5)) & 0x1f, (z - (regionZ << 5)) & 0x1f); if (chunkIn != null) { CompoundTag tag; try (NBTInputStream in = new NBTInputStream(chunkIn)) { tag = (CompoundTag) in.readTag(); } Chunk chunk = (version == SUPPORTED_VERSION_2) ? new ChunkImpl2(tag, maxHeight) : new ChunkImpl(tag, maxHeight); chunks[x - (regionX << 5) + 1][z - (regionZ << 5) + 1] = chunk; } } } } } finally { for (RegionFile regionFile: regionFiles.values()) { regionFile.close(); } } // } } @Override public int getBlockTypeAt(int x, int y, int height) { if (height >= maxHeight) { return BLK_AIR; } Chunk chunk = getChunk(x >> 4, y >> 4); if (chunk != null) { return chunk.getBlockType(x & 0xf, height, y & 0xf); } else { return BLK_AIR; } } @Override public int getDataAt(int x, int y, int height) { if (height >= maxHeight) { return 0; } Chunk chunk = getChunk(x >> 4, y >> 4); if (chunk != null) { return chunk.getDataValue(x & 0xf, height, y & 0xf); } else { return 0; } } @Override public Material getMaterialAt(int x, int y, int height) { if (height >= maxHeight) { return Material.AIR; } Chunk chunk = getChunk(x >> 4, y >> 4); if (chunk != null) { return chunk.getMaterial(x & 0xf, height, y & 0xf); } else { return Material.AIR; } } @Override public void setBlockTypeAt(int x, int y, int height, int blockType) { if (height >= maxHeight) { // Fail silently return; } Chunk chunk = getChunkForEditing(x >> 4, y >> 4); if (chunk != null) { chunk.setBlockType(x & 0xf, height, y & 0xf, blockType); } } @Override public void setDataAt(int x, int y, int height, int data) { if (height >= maxHeight) { // Fail silently return; } Chunk chunk = getChunkForEditing(x >> 4, y >> 4); if (chunk != null) { chunk.setDataValue(x & 0xf, height, y & 0xf, data); } } @Override public void setMaterialAt(int x, int y, int height, Material material) { if (height >= maxHeight) { // Fail silently return; } Chunk chunk = getChunkForEditing(x >> 4, y >> 4); if (chunk != null) { chunk.setMaterial(x & 0xf, height, y & 0xf, material); } } @Override public int getMaxHeight() { return maxHeight; } @Override public void addEntity(int x, int y, int height, Entity entity) { addEntity(x + 0.5, y + 0.5, height + 1.5, entity); } @Override public void addEntity(double x, double y, double height, Entity entity) { Chunk chunk = getChunkForEditing(((int) x) >> 4, ((int) y) >> 4); if (chunk != null) { Entity clone = (Entity) entity.clone(); clone.setPos(new double[] {x, height, y}); chunk.getEntities().add(clone); } } @Override public void addTileEntity(int x, int y, int height, TileEntity tileEntity) { Chunk chunk = getChunkForEditing(x >> 4, y >> 4); if (chunk != null) { TileEntity clone = (TileEntity) tileEntity.clone(); clone.setX(x); clone.setY(height); clone.setZ(y); chunk.getTileEntities().add(clone); } } @Override public int getBlockLightLevel(int x, int y, int height) { if (height >= maxHeight) { return 0; } Chunk chunk = getChunk(x >> 4, y >> 4); if (chunk != null) { return chunk.getBlockLightLevel(x & 0xf, height, y & 0xf); } else { return 0; } } @Override public void setBlockLightLevel(int x, int y, int height, int blockLightLevel) { if (height >= maxHeight) { // Fail silently return; } Chunk chunk = getChunkForEditing(x >> 4, y >> 4); if (chunk != null) { chunk.setBlockLightLevel(x & 0xf, height, y & 0xf, blockLightLevel); } } @Override public int getSkyLightLevel(int x, int y, int height) { if (height >= maxHeight) { return 15; } Chunk chunk = getChunk(x >> 4, y >> 4); if (chunk != null) { return chunk.getSkyLightLevel(x & 0xf, height, y & 0xf); } else { return 0; } } @Override public void setSkyLightLevel(int x, int y, int height, int skyLightLevel) { if (height >= maxHeight) { // Fail silently return; } Chunk chunk = getChunkForEditing(x >> 4, y >> 4); if (chunk != null) { chunk.setSkyLightLevel(x & 0xf, height, y & 0xf, skyLightLevel); } } @Override public boolean isChunkPresent(int x, int z) { x -= regionX << 5; z -= regionZ << 5; if ((x < -1) || (x >= (CHUNKS_PER_SIDE + 1)) || (z < -1) || (z >= (CHUNKS_PER_SIDE + 1))) { return false; } else { return chunks[x + 1][z + 1] != null; } } @Override public Chunk getChunk(int x, int z) { x -= regionX << 5; z -= regionZ << 5; if ((x < -1) || (x >= (CHUNKS_PER_SIDE + 1)) || (z < -1) || (z >= (CHUNKS_PER_SIDE + 1))) { return null; } else { return chunks[x + 1][z + 1]; } } @Override public Chunk getChunkForEditing(int x, int z) { Chunk chunk = getChunk(x, z); if (chunkCreationMode && (chunk == null)) { int localX = x - (regionX << 5); int localZ = z - (regionZ << 5); if ((localX >= 0) && (localX < CHUNKS_PER_SIDE) && (localZ >= 0) && (localZ < CHUNKS_PER_SIDE)) { chunk = (version == SUPPORTED_VERSION_2) ? new ChunkImpl2(x, z, maxHeight) : new ChunkImpl(x, z, maxHeight); chunks[x + 1][z + 1] = chunk; } } return chunk; } @Override public void addChunk(Chunk chunk) { int localX = chunk.getxPos() - (regionX << 5); int localZ = chunk.getzPos() - (regionZ << 5); if ((localX >= -1) && (localX <= CHUNKS_PER_SIDE) && (localZ >= -1) && (localZ <= CHUNKS_PER_SIDE)) { chunks[localX + 1][localZ + 1] = chunk; } } @Override public int getHighestNonAirBlock(int x, int y) { Chunk chunk = getChunk(x >> 4, y >> 4); if (chunk != null) { return chunk.getHighestNonAirBlock(x & 0xf, y & 0xf); } else { return -1; } } public void save(File dimensionDir) throws IOException { File file = new File(dimensionDir, "region/r." + regionX + "." + regionZ + ((version == SUPPORTED_VERSION_2) ? ".mca" : ".mcr")); // synchronized (DISK_ACCESS_MONITOR) { RegionFile regionFile = new RegionFile(file); try { for (int x = 0; x < CHUNKS_PER_SIDE; x++) { for (int z = 0; z < CHUNKS_PER_SIDE; z++) { final Chunk chunk = chunks[x + 1][z + 1]; if (chunk != null) { // Do some sanity checks first // Check that all tile entities for which the chunk // contains data are actually there for (Iterator<TileEntity> i = chunk.getTileEntities().iterator(); i.hasNext(); ) { final TileEntity tileEntity = i.next(); final Set<Integer> blockIds = Constants.TILE_ENTITY_MAP.get(tileEntity.getId()); if (blockIds == null) { logger.warn("Unknown tile entity ID \"" + tileEntity.getId() + "\" encountered @ " + tileEntity.getX() + "," + tileEntity.getZ() + "," + tileEntity.getY() + "; can't check whether the corresponding block is there!"); } else { final int existingBlockId = chunk.getBlockType(tileEntity.getX() & 0xf, tileEntity.getY(), tileEntity.getZ() & 0xf); if (! blockIds.contains(existingBlockId)) { // The block at the specified location // is not a tile entity, or a different // tile entity. Remove the data i.remove(); if (logger.isDebugEnabled()) { logger.debug("Removing tile entity " + tileEntity.getId() + " @ " + tileEntity.getX() + "," + tileEntity.getZ() + "," + tileEntity.getY() + " because the block at that location is a " + BLOCK_TYPE_NAMES[existingBlockId]); } } } } // Check that there aren't multiple tile entities (of the same type, // otherwise they would have been removed above) in the same location Set<Point3i> occupiedCoords = new HashSet<>(); for (Iterator<TileEntity> i = chunk.getTileEntities().iterator(); i.hasNext(); ) { TileEntity tileEntity = i.next(); Point3i coords = new Point3i(tileEntity.getX(), tileEntity.getZ(), tileEntity.getY()); if (occupiedCoords.contains(coords)) { // There is already tile data for that location in the chunk; // remove this copy i.remove(); logger.warn("Removing tile entity " + tileEntity.getId() + " @ " + tileEntity.getX() + "," + tileEntity.getZ() + "," + tileEntity.getY() + " because there is already a tile entity of the same type at that location"); } else { occupiedCoords.add(coords); } } try (NBTOutputStream out = new NBTOutputStream(regionFile.getChunkDataOutputStream(x, z))) { out.writeTag(chunk.toNBT()); } } } } } finally { regionFile.close(); } // } } public boolean isChunkCreationMode() { return chunkCreationMode; } public void setChunkCreationMode(boolean chunkCreationMode) { this.chunkCreationMode = chunkCreationMode; } private final int maxHeight, version; private final Chunk[][] chunks = new Chunk[CHUNKS_PER_SIDE + 2][CHUNKS_PER_SIDE + 2]; private final int regionX, regionZ; private boolean chunkCreationMode; // private static final Object DISK_ACCESS_MONITOR = new Object(); public static final int CHUNKS_PER_SIDE = 32; private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(WorldRegion.class); }