From f7f430e3014b6c3ccee6d69e09b994635c620662 Mon Sep 17 00:00:00 2001 From: WHR Date: Tue, 19 May 2020 10:33:20 +0800 Subject: [PATCH] Add support of old MCRegion (.mcr) format --- .../minecraft/maprend/McRegionChunkData.java | 69 +++++++++ .../maprend/McRegionMiniChunkData.java | 92 +++++++++++ .../togos/minecraft/maprend/RegionMap.java | 31 +++- .../minecraft/maprend/RegionRenderer.java | 143 ++++++++++++------ .../minecraft/maprend/io/RegionFile.java | 15 +- 5 files changed, 297 insertions(+), 53 deletions(-) create mode 100644 src/main/java/togos/minecraft/maprend/McRegionChunkData.java create mode 100644 src/main/java/togos/minecraft/maprend/McRegionMiniChunkData.java diff --git a/src/main/java/togos/minecraft/maprend/McRegionChunkData.java b/src/main/java/togos/minecraft/maprend/McRegionChunkData.java new file mode 100644 index 0000000..2d231cc --- /dev/null +++ b/src/main/java/togos/minecraft/maprend/McRegionChunkData.java @@ -0,0 +1,69 @@ +package togos.minecraft.maprend; + +import java.util.Map; + +import org.jnbt.ByteArrayTag; +import org.jnbt.CompoundTag; +import org.jnbt.IntTag; + +public class McRegionChunkData extends McRegionMiniChunkData +{ + public McRegionChunkData( long px, long py, long pz, int w, int h, int d ) { + super(px,py,pz,w,h,d); + } + + /* + public final int height = 128; // Y/+up/-down + public final int depth = 16; // Z/+west/-east + public final int width = 16; // X/+south/-north + + (N, -x) + | + | + | + | + (W, +z)--------0---------(E, -z) + | + | + | + | + (S, +x) + */ + + public byte[] skyLightData = new byte[(height*depth*width+1)/2]; + public byte[] blockLightData = new byte[(height*depth*width+1)/2]; + public byte[] lightHeightData = new byte[depth*width]; + public boolean terrainPopulated = false; + + //// Sky light //// + + public void setSkyLight( int x, int y, int z, int value ) { + putNybble(skyLightData, blockIndex(x,y,z), value); + } + + //// Light height //// + + public void setLightHeight( int x, int z, int height ) { + lightHeightData[z*width+x] = (byte)(height); + } + + public static McRegionChunkData fromTag( CompoundTag t ) { + Map m = t.getValue(); + IntTag xPos = (IntTag)m.get( "xPos" ); + IntTag zPos = (IntTag)m.get( "zPos" ); + + McRegionChunkData cd = new McRegionChunkData( + 16*xPos.getValue().intValue(), 0, 16*zPos.getValue().intValue(), + 16, 128, 16 + ); + + cd.blocks = ((ByteArrayTag)m.get("Blocks")).getValue(); + cd.block_data = ((ByteArrayTag)m.get("Data")).getValue(); + cd.skyLightData = ((ByteArrayTag)m.get("SkyLight")).getValue(); + cd.blockLightData = ((ByteArrayTag)m.get("BlockLight")).getValue(); + cd.lightHeightData = ((ByteArrayTag)m.get("HeightMap")).getValue(); + // TODO: this part + //cd.tileEntityData = ((CompoundTag)m.get("TileEntities")).getValue(); + return cd; + } +} diff --git a/src/main/java/togos/minecraft/maprend/McRegionMiniChunkData.java b/src/main/java/togos/minecraft/maprend/McRegionMiniChunkData.java new file mode 100644 index 0000000..9d01b3b --- /dev/null +++ b/src/main/java/togos/minecraft/maprend/McRegionMiniChunkData.java @@ -0,0 +1,92 @@ +package togos.minecraft.maprend; + +import java.util.ArrayList; +import java.util.List; + +public class McRegionMiniChunkData +{ + /** + * X,Y,Z coordinates (in blocks a.k.a. world units a.k.a. meters) + * of the bottom northeast corner of the chunk within the world. + */ + public final long posX, posY, posZ; + public final int width, height, depth; + + public byte[] blocks; + public byte[] block_data; + public List tileEntityData = new ArrayList(); + + public McRegionMiniChunkData( long px, long py, long pz, int width, int height, int depth ) { + this.posX = px; + this.posY = py; + this.posZ = pz; + this.width = width; + this.height = height; + this.depth = depth; + this.blocks = new byte[height*depth*width]; + this.block_data = new byte[(height*depth*width+1)/2]; + } + + /* + * Return the + * X,Y,Z coordinates (in blocks a.k.a. world units a.k.a. meters) + * of the bottom northeast corner of the chunk within the world. + */ + public long getChunkPositionX() { return posX; } + public long getChunkPositionY() { return posY; } + public long getChunkPositionZ() { return posZ; } + + public int getChunkWidth() { return width; } + public int getChunkHeight() { return height; } + public int getChunkDepth() { return depth; } + + protected int blockIndex( int x, int y, int z ) { + return y + z*height + x*depth*height; + } + + protected void putNybble( byte[] data, int index, int value ) { + int byteIndex = index>>1; + byte oldValue = data[byteIndex]; + if( (index & 0x1) == 0 ) { + data[ byteIndex ] = (byte)((oldValue & 0xF0) | (value & 0x0F)); + } else { + data[ byteIndex ] = (byte)((oldValue & 0x0F) | ((value<<4) & 0xF0)); + } + } + + protected byte getNybble( byte[] data, int index ) { + int byteIndex = index>>1; + if( (index & 0x1) == 0 ) { + return (byte)((data[ byteIndex ] >> 4) & 0x0F); + } else { + return (byte)((data[ byteIndex ] >> 0) & 0x0F); + } + } + + //// Block //// + + public byte getBlock( int x, int y, int z ) { + return blocks[ blockIndex(x,y,z) ]; + } + + public void setBlockNumber( int x, int y, int z, byte blockNum ) { + blocks[ blockIndex(x,y,z) ] = blockNum; + } + + public byte getBlockExtraBits( int x, int y, int z ) { + return getNybble( block_data, blockIndex(x,y,z) ); + } + + public void setBlockExtraBits( int x, int y, int z, byte value ) { + putNybble( block_data, blockIndex(x,y,z), value ); + } + + public void setBlock( int x, int y, int z, byte blockNum, byte extraBits ) { + setBlockNumber( x, y, z, blockNum ); + setBlockExtraBits( x, y, z, extraBits ); + } + + public void setBlock( int x, int y, int z, byte blockNum ) { + setBlock( x, y, z, blockNum, (byte)0 ); + } +} diff --git a/src/main/java/togos/minecraft/maprend/RegionMap.java b/src/main/java/togos/minecraft/maprend/RegionMap.java index 196b53e..74e9b5a 100644 --- a/src/main/java/togos/minecraft/maprend/RegionMap.java +++ b/src/main/java/togos/minecraft/maprend/RegionMap.java @@ -31,16 +31,37 @@ public void addRegion( Region r ) { if( r.rx >= maxX ) maxX = r.rx+1; if( r.rz >= maxZ ) maxZ = r.rz+1; } - - static final Pattern rfpat = Pattern.compile("^r\\.(-?\\d+)\\.(-?\\d+)\\.mca$"); - + + private void removeOldRegionFiles() { + int i = regions.size() - 1; + while( i >= 0 ) { + String path = regions.get(i).regionFile.getPath(); + int len = path.length(); + if( len > 4 && path.regionMatches(len - 4, ".mcr", 0, 4) ) { + regions.remove(i); + if(i < regions.size()) continue; + } + i--; + } + } + + static final Pattern rfpat = Pattern.compile("^r\\.(-?[0-9]+)\\.(-?[0-9]+)\\.(mc[ar])$"); + static final Pattern arfpat = Pattern.compile("^r\\.(-?[0-9]+)\\.(-?[0-9]+)\\.mca$"); + protected void add( File dir, BoundingRect limit ) { Matcher m; if( dir.isDirectory() ) { + boolean anvil_only = false; File[] files = dir.listFiles(); for( int i=0; i= maxHeight ) continue; - if( absY+16 <= minHeight ) continue; - - if( usedSections[s] ) { - short[] blockIds = sectionBlockIds[s]; - byte[] blockData = sectionBlockData[s]; - - for( int idx=z*16+x, y=0; y<16; ++y, idx+=256, ++absY ) { - if( absY < minHeight || absY >= maxHeight ) continue; - - final short blockId = blockIds[idx]; - final byte blockDatum = blockData[idx]; - int blockColor = getColor( blockId&0xFFFF, blockDatum, biomeId ); - pixelColor = Color.overlay( pixelColor, blockColor ); - if( Color.alpha(blockColor) >= shadeOpacityCutoff ) { - pixelHeight = (short)absY; + + if(rf.isAnvilFormat()) { + int biomeId = biomeIds[z*16+x]&0xFF; + for( int s=0; s= maxHeight ) continue; + if( absY+16 <= minHeight ) continue; + + if( usedSections[s] ) { + short[] blockIds = sectionBlockIds[s]; + byte[] blockData = sectionBlockData[s]; + + for( int idx=z*16+x, y=0; y<16; ++y, idx+=256, ++absY ) { + if( absY < minHeight || absY >= maxHeight ) continue; + + final short blockId = blockIds[idx]; + final byte blockDatum = blockData[idx]; + int blockColor = getColor( blockId&0xFFFF, blockDatum, biomeId ); + pixelColor = Color.overlay( pixelColor, blockColor ); + if( Color.alpha(blockColor) >= shadeOpacityCutoff ) { + pixelHeight = (short)absY; + } } - } - } else { - if( minHeight <= absY && maxHeight >= absY+16 ) { - // Optimize the 16-blocks-of-air case: - pixelColor = Color.overlay( pixelColor, air16Color ); } else { - // TODO: mix + if( minHeight <= absY && maxHeight >= absY+16 ) { + // Optimize the 16-blocks-of-air case: + pixelColor = Color.overlay( pixelColor, air16Color ); + } else { + // TODO: mix + } } } + } else for( int idx=z*128 + x*16*128, y=0; y<128; y++, idx++ ) { + if( y < minHeight ) continue; + if( y >= maxHeight ) break; + final int blockId = correct_signed_byte(old_block_ids[idx]); + final int blockDatum = correct_signed_byte(old_block_data[idx]); + int blockColor = getColor(blockId, blockDatum, 1); + pixelColor = Color.overlay( pixelColor, blockColor ); + if( Color.alpha(blockColor) >= shadeOpacityCutoff ) { + pixelHeight = (short)y; + } } - + final int dIdx = 512*(cz*16+z)+16*cx+x; colors[dIdx] = pixelColor; heights[dIdx] = pixelHeight; @@ -385,11 +436,11 @@ protected static String pad( String v, int targetLength ) { while( v.length() < targetLength ) v = " "+v; return v; } - + protected static String pad( int v, int targetLength ) { - return pad( ""+v, targetLength ); + return pad( String.valueOf(v), targetLength ); } - + public void renderAll( RegionMap rm, File outputDir, boolean force, int threadCount ) throws IOException, InterruptedException { final long startTime = System.currentTimeMillis(); @@ -617,7 +668,7 @@ public void createBigImage( RegionMap rm, File outputDir) { " -scales 1:,... ; list scales at which to render\n" + " -threads ; maximum number of CPU threads to use for rendering\n" + "\n" + - "Input files may be 'region/' directories or individual '.mca' files.\n" + + "Input files may be 'region/' directories, individual '.mca' or '.mcr' files.\n" + "\n" + "tiles.html will always be generated if a single directory is given as input.\n" + "\n" + diff --git a/src/main/java/togos/minecraft/maprend/io/RegionFile.java b/src/main/java/togos/minecraft/maprend/io/RegionFile.java index 88a16d2..b8e22c5 100644 --- a/src/main/java/togos/minecraft/maprend/io/RegionFile.java +++ b/src/main/java/togos/minecraft/maprend/io/RegionFile.java @@ -85,6 +85,7 @@ public class RegionFile private ArrayList sectorFree; private int sizeDelta; private long lastModified = 0; + private Boolean anvil_format; public RegionFile(File path) { offsets = new int[SECTOR_INTS]; @@ -248,7 +249,7 @@ public synchronized DataInputStream getChunkDataInputStream(int x, int z) { public DataOutputStream getChunkDataOutputStream(int x, int z) { if (outOfBounds(x, z)) return null; - + return new DataOutputStream(new DeflaterOutputStream(getChunkOutputStream(x, z, VERSION_DEFLATE))); } @@ -396,4 +397,14 @@ private void setTimestamp(int x, int z, int value) throws IOException { public void close() throws IOException { file.close(); } -} \ No newline at end of file + + public boolean isAnvilFormat() { + if(anvil_format == null) { + String path = fileName.getPath(); + boolean value = path.charAt(path.length() - 1) == 'a'; + anvil_format = Boolean.valueOf(value); + return value; + } + return anvil_format.booleanValue(); + } +}