diff --git a/core/src/main/java/com/meowster/mcquad/McQuad.java b/core/src/main/java/com/meowster/mcquad/McQuad.java index d72d43c..29a26c0 100755 --- a/core/src/main/java/com/meowster/mcquad/McQuad.java +++ b/core/src/main/java/com/meowster/mcquad/McQuad.java @@ -23,7 +23,7 @@ public class McQuad { /* This version is displayed in the masthead of the web app */ - private static final String MCQUAD_VERSION = "1.2.1"; + private static final String MCQUAD_VERSION = "1.2.2"; private final ParsedArgs parsedArgs; private final OutputUtils outputUtils; diff --git a/core/src/main/java/com/meowster/mcquad/McaDump.java b/core/src/main/java/com/meowster/mcquad/McaDump.java new file mode 100644 index 0000000..2a253fe --- /dev/null +++ b/core/src/main/java/com/meowster/mcquad/McaDump.java @@ -0,0 +1,56 @@ +package com.meowster.mcquad; + +import java.io.File; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static com.meowster.util.StringUtils.printErr; +import static java.lang.System.exit; + +/** + * Command-line based region file analysis program, for dumping information + * about MCA (Minecraft Anvil format) files. + *
+ *  Usage: com.meowster.mcquad.McaDump {region file}.
+ * 
+ */ +public class McaDump { + + private static final Pattern RE_REGION_FILE = + Pattern.compile("^r\\.(-?\\d+)\\.(-?\\d+)\\.mca$"); + + private McaDump(File mca) { + Matcher m = RE_REGION_FILE.matcher(mca.getName()); + if (m.matches()) { + int x = Integer.parseInt(m.group(1)); + int z = Integer.parseInt(m.group(2)); + Region region = new Region(x, z, mca); + RegionDataDump.analyze(region); + } else { + printErr("No match?? {}", mca.getName()); + exit(1); + } + } + + private static int usage() { + System.out.println("Usage: McaDump {region file}"); + return 2; + } + + /** + * Main entry point. + * + * @param args command line arguments. + */ + public static void main(String[] args) { + if (args.length != 1) { + exit(usage()); + } + File mca = new File(args[0]); + if (!mca.canRead()) { + printErr("Unable to read file: {}", args[0]); + exit(usage()); + } + new McaDump(mca); + } +} diff --git a/core/src/main/java/com/meowster/mcquad/Region.java b/core/src/main/java/com/meowster/mcquad/Region.java index 3208ea7..90461d0 100755 --- a/core/src/main/java/com/meowster/mcquad/Region.java +++ b/core/src/main/java/com/meowster/mcquad/Region.java @@ -33,7 +33,7 @@ class Region { * @param f the region file * @param mock file is a mock */ - public Region(int x, int z, File f, boolean mock) { + Region(int x, int z, File f, boolean mock) { this.coord = new Coord(x, z); this.f = f; this.mock = mock; @@ -51,6 +51,17 @@ public Region(int x, int z, File f, boolean mock) { this(x, z, null, true); } + /** + * Constructor for creating region file (not a mock). + * + * @param x the x-coord + * @param z the z-coord + * @param f the region file + */ + Region(int x, int z, File f) { + this(x, z, f, false); + } + @Override public String toString() { return "Region{" + coord + ", mock=" + mock + ", f=" + f + '}'; @@ -79,6 +90,15 @@ File regionFile() { return f; } + /** + * Returns true if this is a mock (test) region. + * + * @return true if mock; false otherwise + */ + boolean isMock() { + return mock; + } + /** * Returns the last modified timestamp for the backing region file. * If there is no backing file (for example, unit tests running in @@ -123,6 +143,15 @@ DataInputStream getChunkDataStream(int cx, int cz) { return rf == null ? null : rf.getChunkDataStream(cx, cz); } + /** + * Returns the number of chunks contained in this region. + * + * @return the number of chunks in the region + */ + int chunkCount() { + return rf == null ? 0 : rf.chunkCount(); + } + /** * Instructs the region to release any resources used to hold region data, * since we have generated the image and the data is no longer required. @@ -130,5 +159,4 @@ DataInputStream getChunkDataStream(int cx, int cz) { void releaseResources() { rf = null; } - } diff --git a/core/src/main/java/com/meowster/mcquad/RegionData.java b/core/src/main/java/com/meowster/mcquad/RegionData.java index 5f76280..eb69e96 100755 --- a/core/src/main/java/com/meowster/mcquad/RegionData.java +++ b/core/src/main/java/com/meowster/mcquad/RegionData.java @@ -48,6 +48,18 @@ void load(Set regions) { * @param r the region to load */ private void loadRegion(Region r) { + /* + * NOTE: + * There is a bug in the Minecraft server (observed in 1.10.2) that + * sometimes outputs region files containing a single chunk, with no + * bearing to where players have explored. We need to filter out these + * spurious regions. + */ + if (!r.isMock() && r.chunkCount() < 2) { + printErr("Spurious region detected: {}", r); + return; + } + bounds.add(r.coord()); regionMap.put(r.coord(), r); } diff --git a/core/src/main/java/com/meowster/mcquad/RegionDataDump.java b/core/src/main/java/com/meowster/mcquad/RegionDataDump.java new file mode 100644 index 0000000..6c323c8 --- /dev/null +++ b/core/src/main/java/com/meowster/mcquad/RegionDataDump.java @@ -0,0 +1,88 @@ +package com.meowster.mcquad; + +import java.io.DataInputStream; + +import static com.meowster.util.StringUtils.EOL; +import static com.meowster.util.StringUtils.printOut; + +/** + * Analysis of a region file. + */ +class RegionDataDump { + + private static final String HASH = " #"; + private static final String DOT = " ."; + + private static final int NCHUNKS = 32; // chunk dimension of region + private static final int NBLOCKS = 16; // block dimension of chunk + + private static final int CHUNKS_PER_REGION = NCHUNKS * NCHUNKS; + + + private static Region region; + private static int totalChunks; + private static ChunkStuff[][] data; + private static int singleX; + private static int singleZ; + + /** + * Analyze the region and output results. + * + * @param r the region to analyze + */ + static void analyze(Region r) { + region = r; + totalChunks = 0; + data = new ChunkStuff[NCHUNKS][NCHUNKS]; + + // iterate over the chunks in the region... + for (int cz = 0; cz < NCHUNKS; cz++) { + for (int cx = 0; cx < NCHUNKS; cx++) { + DataInputStream dis = region.getChunkDataStream(cx, cz); + if (dis == null) + continue; + + totalChunks++; + singleX = cx; + singleZ = cz; + +// Chunk chunk = new Chunk(dis); + data[cz][cx] = new ChunkStuff(null); + } + } + + outputResults(); + } + + + private static void outputResults() { + printOut("Region: {}; Chunks: {}/{}", + region.regionFile().getName(), + totalChunks, + CHUNKS_PER_REGION + ); + if (totalChunks == 1) { + printOut(" Chunk coords [{}, {}]", singleX, singleZ); + } +// printOut(createMap()); + } + + private static String createMap() { + StringBuilder sb = new StringBuilder(); + for (int cz = 0; cz < NCHUNKS; cz++) { + for (int cx = 0; cx < NCHUNKS; cx++) { + sb.append(data[cz][cx] != null ? HASH : DOT); + } + sb.append(EOL); + } + return sb.toString(); + } + + + // retains info about a chunk + private static class ChunkStuff { + ChunkStuff(Chunk chunk) { + // capture any data we need for dumping... + } + } +} diff --git a/core/src/main/java/com/meowster/mcquad/RegionFile.java b/core/src/main/java/com/meowster/mcquad/RegionFile.java index f2c6878..d880625 100755 --- a/core/src/main/java/com/meowster/mcquad/RegionFile.java +++ b/core/src/main/java/com/meowster/mcquad/RegionFile.java @@ -186,6 +186,54 @@ else if (version == VERSION_DEFLATE) } } + /** + * Returns the number of chunks available in this region. + * + * @return the chunk count + */ + int chunkCount() { + int nChunks = 0; + for (int cz = 0; cz < NCHUNKS; cz++) { + for (int cx = 0; cx < NCHUNKS; cx++) { + if (chunkAvailableAt(cx, cz)) { + nChunks++; + } + } + } + return nChunks; + } + + /** + * Returns true if there is chunk data at the specified coordinates. + * + * @param x chunk x-coord + * @param z chunk z-coord + * @return true if chunk data is here; false otherwise + */ + boolean chunkAvailableAt(int x, int z) { + if (outOfBounds(x, z)) + return false; + + int offset = getOffset(x, z); + if (offset == 0) + return false; + + final int sectorNumber = offset >> 8; + final int numSectors = offset & 0xff; + + return sectorNumber + numSectors <= nSectors; + } + + /** + * Returns the chunk timestamp for the specified coordinates. + * + * @param x chunk x-coord + * @param z chunk z-coord + * @return true if chunk data is here; false otherwise + */ + int timeStampAt(int x, int z) { + return outOfBounds(x, z) ? 0 : chunkTimestamps[x + z * 32]; + } private boolean outOfBounds(int x, int z) { return x < 0 || x >= NCHUNKS || z < 0 || z >= NCHUNKS; diff --git a/pom.xml b/pom.xml index 8d8102b..3885bd1 100644 --- a/pom.xml +++ b/pom.xml @@ -13,7 +13,7 @@ 4.0.0 - 1.2.1 + 1.2.2 UTF-8 diff --git a/tools/src/bin/mcquad-config.sh b/tools/src/bin/mcquad-config.sh index 7d18b58..36d5d98 100644 --- a/tools/src/bin/mcquad-config.sh +++ b/tools/src/bin/mcquad-config.sh @@ -36,5 +36,5 @@ export MCMAPS_CONTEXT=/mcmaps ## # MCQUAD_VERSION should be set to correct version to pull out of maven repo -export MCQUAD_VERSION=1.2.1 +export MCQUAD_VERSION=1.2.2 diff --git a/tools/src/dev/cpwww.sh b/tools/src/dev/cpwww.sh index dbf479c..73f1cfa 100755 --- a/tools/src/dev/cpwww.sh +++ b/tools/src/dev/cpwww.sh @@ -3,7 +3,7 @@ BACK=$PWD TARBALL=static.tar.gz -VERSION=1.2.1 +VERSION=1.2.2 cd ../../../web/target/mcquad-web-${VERSION}/ echo BUILD TAR diff --git a/tools/src/dev/mcadump.sh b/tools/src/dev/mcadump.sh new file mode 100755 index 0000000..27ecbb1 --- /dev/null +++ b/tools/src/dev/mcadump.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +MCQUAD_VERSION=1.2.2 + +MAIN=com.meowster.mcquad.McaDump + +CP="/Users/simonh/dev/mcquad/McQuad/core/target/classes" + +MCSAVES="/Users/simonh/dev/mcquad/tmp" + +ls "$MCSAVES" + +JAVA=/usr/bin/java + + +for FILE in $(ls "$MCSAVES"); +do + $JAVA -cp $CP $MAIN "$MCSAVES/$FILE" +done + +