Work in progress on supporting maps from non-default platforms
parent
ea4ada6676
commit
3e3ca027fd
|
@ -0,0 +1,2 @@
|
|||
@echo off
|
||||
mvn clean gpg:sign deploy -DaltDeploymentRepository=ossrh-snapshots::default::https://oss.sonatype.org/content/repositories/snapshots
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
<groupId>org.pepsoft.worldpainter</groupId>
|
||||
<artifactId>PluginParent</artifactId>
|
||||
<version>1.2.3</version>
|
||||
<version>1.3.0-SNAPSHOT</version>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<name>PluginParent</name>
|
||||
|
@ -40,7 +40,7 @@
|
|||
<dependency>
|
||||
<groupId>org.pepsoft.worldpainter</groupId>
|
||||
<artifactId>WPGUI</artifactId>
|
||||
<version>2.5.9</version>
|
||||
<version>2.5.13-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
|
|
|
@ -6,6 +6,35 @@ package org.pepsoft.minecraft;
|
|||
* <p>Created by Pepijn on 14-12-2016.
|
||||
*/
|
||||
public interface ChunkStore extends ChunkProvider {
|
||||
/**
|
||||
* Return a total count of all the chunks currently in the store.
|
||||
*
|
||||
* @return The count of all the chunks currently in the store.
|
||||
*/
|
||||
long getChunkCount();
|
||||
|
||||
/**
|
||||
* Visit all known chunks. Note that the order is undefined, allowing the
|
||||
* provider to use as efficient an implementation as is possible. The
|
||||
* provided chunks <em>may</em> be read-only.
|
||||
*
|
||||
* @param visitor The visitor to invoke for each chunk.
|
||||
* @return {@code true} if all chunks were visited; {@code false} if not all
|
||||
* chunks may have been visited because the visitor returned {@code false}.
|
||||
*/
|
||||
boolean visitChunks(ChunkVisitor visitor);
|
||||
|
||||
/**
|
||||
* Visit all known chunks for editing. Note that the order is undefined,
|
||||
* allowing the provider to use as efficient an implementation as is
|
||||
* possible. The provided chunks are guaranteed not to be read-only.
|
||||
*
|
||||
* @param visitor The visitor to invoke for each chunk.
|
||||
* @return {@code true} if all chunks were visited; {@code false} if not all
|
||||
* chunks may have been visited because the visitor returned {@code false}.
|
||||
*/
|
||||
boolean visitChunksForEditing(ChunkVisitor visitor);
|
||||
|
||||
/**
|
||||
* Save a chunk to the store. The chunk is only guaranteerd to have been
|
||||
* written to disk if this operation was performed inside a
|
||||
|
@ -30,4 +59,18 @@ public interface ChunkStore extends ChunkProvider {
|
|||
* {@link #doInTransaction(Runnable)}.
|
||||
*/
|
||||
void flush();
|
||||
|
||||
/**
|
||||
* A visitor of chunks.
|
||||
*/
|
||||
@FunctionalInterface interface ChunkVisitor {
|
||||
/**
|
||||
* Visit a chunk.
|
||||
*
|
||||
* @param chunk The chunk to be visited.
|
||||
* @return {@code true} if more chunks should be visited, or
|
||||
* {@code false} if no more chunks need to be visited.
|
||||
*/
|
||||
boolean visitChunk(Chunk chunk);
|
||||
}
|
||||
}
|
|
@ -276,6 +276,16 @@ public final class RegionFile implements AutoCloseable {
|
|||
return readOnly;
|
||||
}
|
||||
|
||||
public int getChunkCount() {
|
||||
int count = 0;
|
||||
for (int offset: offsets) {
|
||||
if (offset != 0) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return fileName.getPath();
|
||||
|
|
|
@ -0,0 +1,108 @@
|
|||
package org.pepsoft.worldpainter;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import org.pepsoft.minecraft.*;
|
||||
import org.pepsoft.minecraft.mapexplorer.JavaMapRecognizer;
|
||||
import org.pepsoft.worldpainter.exporting.*;
|
||||
import org.pepsoft.worldpainter.mapexplorer.MapRecognizer;
|
||||
import org.pepsoft.worldpainter.plugins.AbstractPlugin;
|
||||
import org.pepsoft.worldpainter.plugins.BlockBasedPlatformProvider;
|
||||
import org.pepsoft.worldpainter.util.MinecraftUtil;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static com.google.common.primitives.Ints.toArray;
|
||||
import static org.pepsoft.worldpainter.Constants.*;
|
||||
import static org.pepsoft.worldpainter.DefaultPlugin.JAVA_ANVIL;
|
||||
import static org.pepsoft.worldpainter.DefaultPlugin.JAVA_MCREGION;
|
||||
import static org.pepsoft.worldpainter.util.MinecraftUtil.getRegionDir;
|
||||
|
||||
/**
|
||||
* Created by Pepijn on 9-3-2017.
|
||||
*/
|
||||
public class DefaultPlatformProvider extends AbstractPlugin implements BlockBasedPlatformProvider {
|
||||
public DefaultPlatformProvider() {
|
||||
super("DefaultPlatforms", Version.VERSION);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Platform> getKeys() {
|
||||
return PLATFORMS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] getDimensions(Platform platform, File worldDir) {
|
||||
ensurePlatformSupported(platform);
|
||||
List<Integer> dimensions = new ArrayList<>();
|
||||
for (int dim: new int[]{DIM_NORMAL, DIM_NETHER, DIM_END}) {
|
||||
if (containsFiles(getRegionDir(worldDir, dim))) {
|
||||
dimensions.add(dim);
|
||||
}
|
||||
}
|
||||
return toArray(dimensions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Chunk createChunk(Platform platform, int x, int z, int maxHeight) {
|
||||
if (platform.equals(JAVA_MCREGION)) {
|
||||
return new ChunkImpl(x, z, maxHeight);
|
||||
} else if (platform.equals(JAVA_ANVIL)) {
|
||||
return new ChunkImpl2(x, z, maxHeight);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Platform " + platform + " not supported");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChunkStore getChunkStore(Platform platform, File worldDir, int dimension) {
|
||||
ensurePlatformSupported(platform);
|
||||
Level level;
|
||||
File levelDatFile = new File(worldDir, "level.dat");
|
||||
try {
|
||||
level = Level.load(levelDatFile);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("I/O error while trying to read level.dat", e);
|
||||
}
|
||||
return new JavaChunkStore(platform, getRegionDir(worldDir, dimension), false, null, level.getMaxHeight());
|
||||
}
|
||||
|
||||
@Override
|
||||
public WorldExporter getExporter(World2 world) {
|
||||
Platform platform = world.getPlatform();
|
||||
ensurePlatformSupported(platform);
|
||||
return new JavaWorldExporter(world);
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getDefaultExportDir(Platform platform) {
|
||||
File minecraftDir = MinecraftUtil.findMinecraftDir();
|
||||
return (minecraftDir != null) ? new File(minecraftDir, "saves") : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PostProcessor getPostProcessor(Platform platform) {
|
||||
ensurePlatformSupported(platform);
|
||||
return new JavaPostProcessor();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MapRecognizer getMapRecognizer() {
|
||||
return new JavaMapRecognizer();
|
||||
}
|
||||
|
||||
private void ensurePlatformSupported(Platform platform) {
|
||||
if (! PLATFORMS.contains(platform)) {
|
||||
throw new IllegalArgumentException("Platform " + platform + " not supported");
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("ConstantConditions") // Yes, we just checked that
|
||||
private boolean containsFiles(File dir) {
|
||||
return dir.isDirectory() && (dir.listFiles().length > 0);
|
||||
}
|
||||
|
||||
private static final List<Platform> PLATFORMS = ImmutableList.of(JAVA_ANVIL, JAVA_MCREGION);
|
||||
}
|
|
@ -973,8 +973,4 @@ public abstract class AbstractWorldExporter implements WorldExporter {
|
|||
*/
|
||||
public List<Fixup> fixups;
|
||||
}
|
||||
|
||||
interface RegionVisitor {
|
||||
void visitRegion(WorldRegion region);
|
||||
}
|
||||
}
|
|
@ -19,6 +19,7 @@ import java.io.File;
|
|||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.*;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static org.pepsoft.minecraft.Block.BLOCK_TYPE_NAMES;
|
||||
import static org.pepsoft.minecraft.Constants.*;
|
||||
|
@ -40,6 +41,30 @@ public class JavaChunkStore implements ChunkStore {
|
|||
this.maxHeight = maxHeight;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getChunkCount() {
|
||||
long[] chunkCount = {0L};
|
||||
try {
|
||||
visitRegions(region -> {
|
||||
chunkCount[0] += region.getChunkCount();
|
||||
return true;
|
||||
}, true);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("I/O error while visiting regions of " + regionDir, e);
|
||||
}
|
||||
return chunkCount[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean visitChunks(ChunkVisitor visitor) {
|
||||
return visitChunks(visitor, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean visitChunksForEditing(ChunkVisitor visitor) {
|
||||
return visitChunks(visitor, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveChunk(Chunk chunk) {
|
||||
// chunksSaved++;
|
||||
|
@ -215,7 +240,58 @@ public class JavaChunkStore implements ChunkStore {
|
|||
return regionFile;
|
||||
}
|
||||
|
||||
// private void updateStatistics() {
|
||||
private RegionFile openRegionFile(Point regionCoords) throws IOException {
|
||||
File file = new File(regionDir, "r." + regionCoords.x + "." + regionCoords.y + (platform.equals(JAVA_MCREGION) ? ".mcr" : ".mca"));
|
||||
return file.exists() ? new RegionFile(file) : null;
|
||||
}
|
||||
|
||||
private RegionFile openOrCreateRegionFile(Point regionCoords) throws IOException {
|
||||
File file = new File(regionDir, "r." + regionCoords.x + "." + regionCoords.y + (platform.equals(JAVA_MCREGION) ? ".mcr" : ".mca"));
|
||||
return new RegionFile(file);
|
||||
}
|
||||
|
||||
@FunctionalInterface interface RegionVisitor {
|
||||
boolean visitRegion(RegionFile region) throws IOException;
|
||||
}
|
||||
|
||||
private boolean visitRegions(RegionVisitor visitor, boolean readOnly) throws IOException {
|
||||
final Pattern regionFilePattern = (platform == JAVA_MCREGION)
|
||||
? Pattern.compile("r\\.-?\\d+\\.-?\\d+\\.mcr")
|
||||
: Pattern.compile("r\\.-?\\d+\\.-?\\d+\\.mca");
|
||||
for (File file: regionDir.listFiles((dir, name) -> regionFilePattern.matcher(name).matches())) {
|
||||
try (RegionFile regionFile = new RegionFile(file, readOnly)) {
|
||||
if (! visitor.visitRegion(regionFile)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean visitChunks(ChunkVisitor visitor, boolean readOnly) {
|
||||
try {
|
||||
return visitRegions(region -> {
|
||||
for (int x = 0; x < 32; x++) {
|
||||
for (int z = 0; z < 32; z++) {
|
||||
if (region.containsChunk(x, z)) {
|
||||
try (NBTInputStream in = new NBTInputStream(region.getChunkDataInputStream(x & 31, z & 31))) {
|
||||
CompoundTag tag = (CompoundTag) in.readTag();
|
||||
Chunk chunk = platform.equals(JAVA_MCREGION) ? new ChunkImpl(tag, maxHeight, readOnly) : new ChunkImpl2(tag, maxHeight, readOnly);
|
||||
if (! visitor.visitChunk(chunk)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}, readOnly);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("I/O error while visiting regions of " + regionDir, e);
|
||||
}
|
||||
}
|
||||
|
||||
// private void updateStatistics() {
|
||||
// long now = System.currentTimeMillis();
|
||||
// if ((now - lastStatisticsTimestamp) > 5000) {
|
||||
// float elapsed = (now - lastStatisticsTimestamp) / 1000f;
|
||||
|
|
|
@ -4,7 +4,9 @@
|
|||
*/
|
||||
package org.pepsoft.worldpainter.importing;
|
||||
|
||||
import org.jnbt.*;
|
||||
import org.jnbt.CompoundTag;
|
||||
import org.jnbt.NBTInputStream;
|
||||
import org.jnbt.Tag;
|
||||
import org.pepsoft.minecraft.*;
|
||||
import org.pepsoft.util.ProgressReceiver;
|
||||
import org.pepsoft.util.SubProgressReceiver;
|
||||
|
@ -14,7 +16,6 @@ import org.pepsoft.worldpainter.history.HistoryEntry;
|
|||
import org.pepsoft.worldpainter.layers.*;
|
||||
import org.pepsoft.worldpainter.layers.exporters.FrostExporter.FrostSettings;
|
||||
import org.pepsoft.worldpainter.layers.exporters.ResourcesExporter.ResourcesExporterSettings;
|
||||
import org.pepsoft.worldpainter.plugins.PlatformManager;
|
||||
import org.pepsoft.worldpainter.themes.SimpleTheme;
|
||||
import org.pepsoft.worldpainter.vo.EventVO;
|
||||
|
||||
|
@ -22,28 +23,32 @@ import java.awt.*;
|
|||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.StringWriter;
|
||||
import java.util.*;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static org.pepsoft.minecraft.Constants.*;
|
||||
import static org.pepsoft.minecraft.Material.*;
|
||||
import static org.pepsoft.worldpainter.Constants.*;
|
||||
import static org.pepsoft.worldpainter.DefaultPlugin.*;
|
||||
import static org.pepsoft.worldpainter.DefaultPlugin.JAVA_ANVIL;
|
||||
import static org.pepsoft.worldpainter.DefaultPlugin.JAVA_MCREGION;
|
||||
import static org.pepsoft.worldpainter.biomeschemes.Minecraft1_13Biomes.BIOME_NAMES;
|
||||
import static org.pepsoft.worldpainter.biomeschemes.Minecraft1_13Biomes.HIGHEST_BIOME_ID;
|
||||
|
||||
/**
|
||||
* An importer of Minecraft-like maps (with Minecraft-compatible
|
||||
* {@code level.dat} files and based on {@link BlockBasedPlatformProvider}s.
|
||||
*
|
||||
* @author pepijn
|
||||
*/
|
||||
public class JavaMapImporter extends MapImporter {
|
||||
public JavaMapImporter(TileFactory tileFactory, File levelDatFile, boolean populateNewChunks, Set<Point> chunksToSkip, ReadOnlyOption readOnlyOption, Set<Integer> dimensionsToImport) {
|
||||
public JavaMapImporter(Platform platform, TileFactory tileFactory, File levelDatFile, boolean populateNewChunks, Set<Point> chunksToSkip, ReadOnlyOption readOnlyOption, Set<Integer> dimensionsToImport) {
|
||||
if ((tileFactory == null) || (levelDatFile == null) || (readOnlyOption == null) || (dimensionsToImport == null)) {
|
||||
throw new NullPointerException();
|
||||
}
|
||||
if (! levelDatFile.isFile()) {
|
||||
throw new IllegalArgumentException(levelDatFile + " does not exist or is not a regular file");
|
||||
}
|
||||
this.platform = platform;
|
||||
this.tileFactory = tileFactory;
|
||||
this.levelDatFile = levelDatFile;
|
||||
this.populateNewChunks = populateNewChunks;
|
||||
|
@ -51,7 +56,7 @@ public class JavaMapImporter extends MapImporter {
|
|||
this.readOnlyOption = readOnlyOption;
|
||||
this.dimensionsToImport = dimensionsToImport;
|
||||
}
|
||||
|
||||
|
||||
public World2 doImport(ProgressReceiver progressReceiver) throws IOException, ProgressReceiver.OperationCancelled {
|
||||
long start = System.currentTimeMillis();
|
||||
|
||||
|
@ -241,108 +246,63 @@ public class JavaMapImporter extends MapImporter {
|
|||
}
|
||||
final int maxHeight = dimension.getMaxHeight();
|
||||
final int maxY = maxHeight - 1;
|
||||
final JavaPlatformProvider platformProvider = (JavaPlatformProvider) PlatformManager.getInstance().getPlatformProvider(platform);
|
||||
final File[] regionFiles = platformProvider.getRegionFiles(platform, regionDir);
|
||||
if ((regionFiles == null) || (regionFiles.length == 0)) {
|
||||
throw new RuntimeException("The " + dimension.getName() + " dimension of this map has no region files!");
|
||||
}
|
||||
final Set<Point> newChunks = new HashSet<>();
|
||||
final Set<String> manMadeBlockTypes = new HashSet<>();
|
||||
final Set<Integer> unknownBiomes = new HashSet<>();
|
||||
final boolean importBiomes = dimension.getDim() == DIM_NORMAL;
|
||||
final int total = regionFiles.length * 1024;
|
||||
int count = 0;
|
||||
final ChunkStore chunkStore = PlatformManager.getInstance().getChunkStore(platform, regionDir, dimension.getDim());
|
||||
final long total = chunkStore.getChunkCount();
|
||||
final AtomicInteger count = new AtomicInteger();
|
||||
final StringBuilder reportBuilder = new StringBuilder();
|
||||
for (File file: regionFiles) {
|
||||
if (! chunkStore.visitChunks(chunk -> {
|
||||
try {
|
||||
try (RegionFile regionFile = new RegionFile(file, true)) {
|
||||
for (int x = 0; x < 32; x++) {
|
||||
for (int z = 0; z < 32; z++) {
|
||||
if (progressReceiver != null) {
|
||||
progressReceiver.setProgress((float) count / total);
|
||||
count++;
|
||||
}
|
||||
final Point chunkCoords = new Point((regionFile.getX() << 5) | x, (regionFile.getZ() << 5) | z);
|
||||
if ((chunksToSkip != null) && chunksToSkip.contains(chunkCoords)) {
|
||||
continue;
|
||||
}
|
||||
if (regionFile.containsChunk(x, z)) {
|
||||
final Chunk chunk;
|
||||
try {
|
||||
final InputStream chunkData = regionFile.getChunkDataInputStream(x, z);
|
||||
if (chunkData == null) {
|
||||
// This should never happen, since we checked
|
||||
// with isChunkPresent(), but in practice it
|
||||
// does. Perhaps corrupted data?
|
||||
reportBuilder.append("Missing chunk data for chunk " + x + ", " + z + " in " + file + "; skipping chunk" + EOL);
|
||||
logger.warn("Missing chunk data for chunk " + x + ", " + z + " in " + file + "; skipping chunk");
|
||||
continue;
|
||||
}
|
||||
try (NBTInputStream in = new NBTInputStream(chunkData)) {
|
||||
chunk = platformProvider.createChunk(platform, in.readTag(), maxHeight);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
reportBuilder.append("I/O error while reading chunk " + x + ", " + z + " from file " + file + " (message: \"" + e.getMessage() + "\"); skipping chunk" + EOL);
|
||||
logger.error("I/O error while reading chunk " + x + ", " + z + " from file " + file + "; skipping chunk", e);
|
||||
continue;
|
||||
} catch (IllegalArgumentException e) {
|
||||
reportBuilder.append("Illegal argument exception while reading chunk " + x + ", " + z + " from file " + file + " (message: \"" + e.getMessage() + "\"); skipping chunk" + EOL);
|
||||
logger.error("Illegal argument exception while reading chunk " + x + ", " + z + " from file " + file + "; skipping chunk", e);
|
||||
continue;
|
||||
} catch (NegativeArraySizeException e) {
|
||||
reportBuilder.append("Negative array size exception while reading chunk " + x + ", " + z + " from file " + file + " (message: \"" + e.getMessage() + "\"); skipping chunk" + EOL);
|
||||
logger.error("Negative array size exception while reading chunk " + x + ", " + z + " from file " + file + "; skipping chunk", e);
|
||||
continue;
|
||||
} catch (ClassCastException e) {
|
||||
reportBuilder.append("Class cast exception while reading chunk " + x + ", " + z + " from file " + file + " (message: \"" + e.getMessage() + "\"); skipping chunk" + EOL);
|
||||
logger.error("Class cast exception while reading chunk " + x + ", " + z + " from file " + file + "; skipping chunk", e);
|
||||
continue;
|
||||
}
|
||||
if (progressReceiver != null) {
|
||||
progressReceiver.setProgress((float) count.getAndIncrement() / total);
|
||||
}
|
||||
final Point chunkCoords = chunk.getCoords();
|
||||
if ((chunksToSkip != null) && chunksToSkip.contains(chunkCoords)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((chunk instanceof MC113AnvilChunk) && (((MC113AnvilChunk) chunk).getStatus() == MC113AnvilChunk.Status.EMPTY)) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Skipping \"empty\" chunk at {},{}", chunk.getxPos(), chunk.getzPos());
|
||||
}
|
||||
continue;
|
||||
}
|
||||
final int chunkX = chunkCoords.x;
|
||||
final int chunkZ = chunkCoords.y;
|
||||
final Point tileCoords = new Point(chunkX >> 3, chunkZ >> 3);
|
||||
Tile tile = dimension.getTile(tileCoords);
|
||||
if (tile == null) {
|
||||
tile = dimension.getTileFactory().createTile(tileCoords.x, tileCoords.y);
|
||||
for (int xx = 0; xx < 8; xx++) {
|
||||
for (int yy = 0; yy < 8; yy++) {
|
||||
newChunks.add(new Point((tileCoords.x << TILE_SIZE_BITS) | (xx << 4), (tileCoords.y << TILE_SIZE_BITS) | (yy << 4)));
|
||||
}
|
||||
}
|
||||
dimension.addTile(tile);
|
||||
}
|
||||
newChunks.remove(new Point(chunkX << 4, chunkZ << 4));
|
||||
|
||||
final Point tileCoords = new Point(chunk.getxPos() >> 3, chunk.getzPos() >> 3);
|
||||
Tile tile = dimension.getTile(tileCoords);
|
||||
if (tile == null) {
|
||||
tile = dimension.getTileFactory().createTile(tileCoords.x, tileCoords.y);
|
||||
for (int xx = 0; xx < 8; xx++) {
|
||||
for (int yy = 0; yy < 8; yy++) {
|
||||
newChunks.add(new Point((tileCoords.x << TILE_SIZE_BITS) | (xx << 4), (tileCoords.y << TILE_SIZE_BITS) | (yy << 4)));
|
||||
}
|
||||
}
|
||||
dimension.addTile(tile);
|
||||
}
|
||||
newChunks.remove(new Point(chunk.getxPos() << 4, chunk.getzPos() << 4));
|
||||
|
||||
boolean manMadeStructuresBelowGround = false;
|
||||
boolean manMadeStructuresAboveGround = false;
|
||||
try {
|
||||
for (int xx = 0; xx < 16; xx++) {
|
||||
for (int zz = 0; zz < 16; zz++) {
|
||||
float height = -1.0f;
|
||||
int waterLevel = 0;
|
||||
boolean floodWithLava = false, frost = false;
|
||||
Terrain terrain = Terrain.BEDROCK;
|
||||
for (int y = maxY; y >= 0; y--) {
|
||||
Material material = chunk.getMaterial(xx, y, zz);
|
||||
boolean manMadeStructuresBelowGround = false;
|
||||
boolean manMadeStructuresAboveGround = false;
|
||||
try {
|
||||
for (int xx = 0; xx < 16; xx++) {
|
||||
for (int zz = 0; zz < 16; zz++) {
|
||||
float height = -1.0f;
|
||||
int waterLevel = 0;
|
||||
boolean floodWithLava = false, frost = false;
|
||||
Terrain terrain = Terrain.BEDROCK;
|
||||
for (int y = Math.min(maxY, chunk.getHighestNonAirBlock(xx, zz)); y >= 0; y--) {
|
||||
Material material = chunk.getMaterial(xx, y, zz);
|
||||
if (!material.natural) {
|
||||
if (height == -1.0f) {
|
||||
manMadeStructuresAboveGround = true;
|
||||
} else {
|
||||
manMadeStructuresBelowGround = true;
|
||||
}
|
||||
if (height == -1.0f) {
|
||||
manMadeStructuresAboveGround = true;
|
||||
} else {
|
||||
manMadeStructuresBelowGround = true;
|
||||
}
|
||||
manMadeBlockTypes.add(material.name);
|
||||
}
|
||||
String name = material.name;
|
||||
if ((name == MC_SNOW) || (name == MC_ICE)) {
|
||||
frost = true;
|
||||
}
|
||||
if ((waterLevel == 0) && ((name == MC_ICE)
|
||||
String name = material.name;
|
||||
if ((name == MC_SNOW) || (name == MC_ICE)) {
|
||||
frost = true;
|
||||
}
|
||||
if ((waterLevel == 0) && ((name == MC_ICE)
|
||||
|| (name == MC_FROSTED_ICE)
|
||||
|| (name == MC_BUBBLE_COLUMN)
|
||||
|| (((name == MC_WATER) || (name == MC_LAVA))
|
||||
|
@ -354,9 +314,9 @@ public class JavaMapImporter extends MapImporter {
|
|||
}
|
||||
} else if (height == -1.0f) {
|
||||
if (TERRAIN_MAPPING.containsKey(name)) {
|
||||
// Terrain found
|
||||
height = y - 0.4375f; // Value that falls in the middle of the lowest one eighth which will still round to the same integer value and will receive a one layer thick smooth snow block (principle of least surprise)
|
||||
terrain = TERRAIN_MAPPING.get(name);
|
||||
// Terrain found
|
||||
height = y - 0.4375f; // Value that falls in the middle of the lowest one eighth which will still round to the same integer value and will receive a one layer thick smooth snow block (principle of least surprise)
|
||||
terrain = TERRAIN_MAPPING.get(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -367,30 +327,30 @@ public class JavaMapImporter extends MapImporter {
|
|||
if (materialAbove.isNamed(MC_SNOW)) {
|
||||
int layers = materialAbove.getProperty(LAYERS);
|
||||
height += layers * 0.125;
|
||||
}
|
||||
}
|
||||
if ((waterLevel == 0) && (height >= 61.5f)) {
|
||||
waterLevel = 62;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ((waterLevel == 0) && (height >= 61.5f)) {
|
||||
waterLevel = 62;
|
||||
}
|
||||
|
||||
final int blockX = (chunk.getxPos() << 4) | xx;
|
||||
final int blockY = (chunk.getzPos() << 4) | zz;
|
||||
final Point coords = new Point(blockX, blockY);
|
||||
dimension.setTerrainAt(coords, terrain);
|
||||
dimension.setHeightAt(coords, Math.max(height, 0.0f));
|
||||
dimension.setWaterLevelAt(blockX, blockY, waterLevel);
|
||||
if (frost) {
|
||||
dimension.setBitLayerValueAt(Frost.INSTANCE, blockX, blockY, true);
|
||||
}
|
||||
if (floodWithLava) {
|
||||
dimension.setBitLayerValueAt(FloodWithLava.INSTANCE, blockX, blockY, true);
|
||||
}
|
||||
if (height == -1.0f) {
|
||||
dimension.setBitLayerValueAt(org.pepsoft.worldpainter.layers.Void.INSTANCE, blockX, blockY, true);
|
||||
}
|
||||
if (importBiomes && chunk.isBiomesAvailable()) {
|
||||
final int biome = chunk.getBiome(xx, zz);
|
||||
if (((biome > HIGHEST_BIOME_ID) || (BIOME_NAMES[biome] == null)) && (biome != 255)) {
|
||||
final int blockX = (chunkX << 4) | xx;
|
||||
final int blockY = (chunkZ << 4) | zz;
|
||||
final Point coords = new Point(blockX, blockY);
|
||||
dimension.setTerrainAt(coords, terrain);
|
||||
dimension.setHeightAt(coords, Math.max(height, 0.0f));
|
||||
dimension.setWaterLevelAt(blockX, blockY, waterLevel);
|
||||
if (frost) {
|
||||
dimension.setBitLayerValueAt(Frost.INSTANCE, blockX, blockY, true);
|
||||
}
|
||||
if (floodWithLava) {
|
||||
dimension.setBitLayerValueAt(FloodWithLava.INSTANCE, blockX, blockY, true);
|
||||
}
|
||||
if (height == -1.0f) {
|
||||
dimension.setBitLayerValueAt(org.pepsoft.worldpainter.layers.Void.INSTANCE, blockX, blockY, true);
|
||||
}
|
||||
if (importBiomes && chunk.isBiomesAvailable()) {
|
||||
final int biome = chunk.getBiome(xx, zz);
|
||||
if (((biome > HIGHEST_BIOME_ID) || (BIOME_NAMES[biome] == null)) && (biome != 255)) {
|
||||
unknownBiomes.add(biome);
|
||||
}
|
||||
// If the biome is set (around the edges of the map Minecraft sets it to
|
||||
|
@ -405,28 +365,27 @@ public class JavaMapImporter extends MapImporter {
|
|||
}
|
||||
}
|
||||
} catch (NullPointerException e) {
|
||||
reportBuilder.append("Null pointer exception while reading chunk " + x + ", " + z + " from file " + file + "; skipping chunk" + EOL);
|
||||
logger.error("Null pointer exception while reading chunk " + x + ", " + z + " from file " + file + "; skipping chunk", e);
|
||||
continue;
|
||||
} catch (ArrayIndexOutOfBoundsException e) {
|
||||
reportBuilder.append("Array index out of bounds while reading chunk " + x + ", " + z + " from file " + file + " (message: \"" + e.getMessage() + "\"); skipping chunk" + EOL);
|
||||
logger.error("Array index out of bounds while reading chunk " + x + ", " + z + " from file " + file + "; skipping chunk", e);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (((readOnlyOption == ReadOnlyOption.MAN_MADE) && (manMadeStructuresBelowGround || manMadeStructuresAboveGround))
|
||||
|| ((readOnlyOption == ReadOnlyOption.MAN_MADE_ABOVE_GROUND) && manMadeStructuresAboveGround)
|
||||
|| (readOnlyOption == ReadOnlyOption.ALL)) {
|
||||
dimension.setBitLayerValueAt(ReadOnly.INSTANCE, chunk.getxPos() << 4, chunk.getzPos() << 4, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
reportBuilder.append("Null pointer exception while reading chunk " + chunkX + "," + chunkZ + "; skipping chunk" + EOL);
|
||||
logger.error("Null pointer exception while reading chunk " + chunkX + "," + chunkZ + "; skipping chunk", e);
|
||||
return true;
|
||||
} catch (ArrayIndexOutOfBoundsException e) {
|
||||
reportBuilder.append("Array index out of bounds while reading chunk " + chunkX + "," + chunkZ + " (message: \"" + e.getMessage() + "\"); skipping chunk" + EOL);
|
||||
logger.error("Array index out of bounds while reading chunk " + chunkX + "," + chunkZ + "; skipping chunk", e);
|
||||
return true;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
reportBuilder.append("I/O error while opening region file " + file + " (message: \"" + e.getMessage() + "\"); skipping region" + EOL);
|
||||
logger.error("I/O error while opening region file " + file + "; skipping region", e);
|
||||
|
||||
if (((readOnlyOption == ReadOnlyOption.MAN_MADE) && (manMadeStructuresBelowGround || manMadeStructuresAboveGround))
|
||||
|| ((readOnlyOption == ReadOnlyOption.MAN_MADE_ABOVE_GROUND) && manMadeStructuresAboveGround)
|
||||
|| (readOnlyOption == ReadOnlyOption.ALL)) {
|
||||
dimension.setBitLayerValueAt(ReadOnly.INSTANCE, chunkX << 4, chunkZ << 4, true);
|
||||
}
|
||||
} catch (ProgressReceiver.OperationCancelled e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
})) {
|
||||
throw new ProgressReceiver.OperationCancelled("Operation cancelled");
|
||||
}
|
||||
|
||||
// Process chunks that were only added to fill out a tile
|
||||
|
@ -448,7 +407,8 @@ public class JavaMapImporter extends MapImporter {
|
|||
|
||||
return reportBuilder.length() != 0 ? reportBuilder.toString() : null;
|
||||
}
|
||||
|
||||
|
||||
private final Platform platform;
|
||||
private final TileFactory tileFactory;
|
||||
private final File levelDatFile;
|
||||
private final boolean populateNewChunks;
|
||||
|
|
|
@ -2,6 +2,7 @@ package org.pepsoft.worldpainter.plugins;
|
|||
|
||||
import org.pepsoft.minecraft.Chunk;
|
||||
import org.pepsoft.minecraft.ChunkStore;
|
||||
import org.pepsoft.worldpainter.Constants;
|
||||
import org.pepsoft.worldpainter.Platform;
|
||||
import org.pepsoft.worldpainter.exporting.PostProcessor;
|
||||
|
||||
|
|
|
@ -6,9 +6,12 @@ import org.pepsoft.worldpainter.Platform;
|
|||
import org.pepsoft.worldpainter.World2;
|
||||
import org.pepsoft.worldpainter.exporting.PostProcessor;
|
||||
import org.pepsoft.worldpainter.exporting.WorldExporter;
|
||||
import org.pepsoft.worldpainter.mapexplorer.MapRecognizer;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Created by Pepijn on 12-2-2017.
|
||||
|
@ -46,6 +49,37 @@ public class PlatformManager extends AbstractProviderManager<Platform, PlatformP
|
|||
return ((BlockBasedPlatformProvider) getImplementation(platform)).getPostProcessor(platform);
|
||||
}
|
||||
|
||||
/**
|
||||
* Identify the platform provider for a map.
|
||||
*
|
||||
* @param worldDir The directory to identify.
|
||||
* @return The platform provider which supports the format of the specified
|
||||
* map, or {@code null} if no platform provider claimed support.
|
||||
*/
|
||||
public PlatformProvider identifyMap(File worldDir) {
|
||||
Set<PlatformProvider> candidates = new HashSet<>();
|
||||
for (PlatformProvider provider: getImplementations()) {
|
||||
MapRecognizer mapRecognizer = provider.getMapRecognizer();
|
||||
if ((mapRecognizer != null) && mapRecognizer.isMap(worldDir)) {
|
||||
candidates.add(provider);
|
||||
}
|
||||
}
|
||||
if (candidates.isEmpty()) {
|
||||
return null;
|
||||
} else if (candidates.size() == 1) {
|
||||
return candidates.iterator().next();
|
||||
} else {
|
||||
// If one of the candidates is ourselves, discount it, assuming that
|
||||
// the plugin did a more specific check and is probably right
|
||||
candidates.removeIf(provider -> provider.getName().equals("DefaultPlatforms"));
|
||||
if (candidates.size() == 1) {
|
||||
return candidates.iterator().next();
|
||||
} else {
|
||||
throw new RuntimeException("Multiple platform providers (" + candidates + ") claimed support for this map");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static PlatformManager getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
|
|
@ -4,10 +4,6 @@
|
|||
*/
|
||||
package org.pepsoft.worldpainter.util;
|
||||
|
||||
import org.jnbt.CompoundTag;
|
||||
import org.jnbt.NBTInputStream;
|
||||
import org.jnbt.Tag;
|
||||
import org.pepsoft.minecraft.*;
|
||||
import org.pepsoft.util.SystemUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
@ -21,6 +17,8 @@ import static org.pepsoft.minecraft.Constants.DATA_VERSION_MC_1_12_2;
|
|||
import static org.pepsoft.minecraft.Constants.VERSION_MCREGION;
|
||||
import static org.pepsoft.worldpainter.Constants.*;
|
||||
|
||||
import static org.pepsoft.worldpainter.Constants.*;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author pepijn
|
||||
|
@ -89,19 +87,9 @@ public class MinecraftUtil {
|
|||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Visit all the chunks of a Minecraft map.
|
||||
*
|
||||
* @param worldDir The map directory.
|
||||
* @param dimension The ordinal of the dimension to visit.
|
||||
* @param visitor The visitor to invoke for each chunk.
|
||||
* @throws IOException If an I/O error occurs while reading the chunks.
|
||||
*/
|
||||
public static void visitChunks(File worldDir, int dimension, ChunkVisitor visitor) throws IOException {
|
||||
final File levelDatFile = new File(worldDir, "level.dat");
|
||||
final Level level = Level.load(levelDatFile);
|
||||
final int version = level.getVersion(), maxHeight = level.getMaxHeight(), dataVersion = level.getDataVersion();
|
||||
final File regionDir;
|
||||
@NotNull
|
||||
public static File getRegionDir(File worldDir, int dimension) {
|
||||
File regionDir;
|
||||
switch (dimension) {
|
||||
case DIM_NORMAL:
|
||||
regionDir = new File(worldDir, "region");
|
||||
|
@ -113,45 +101,9 @@ public class MinecraftUtil {
|
|||
regionDir = new File(worldDir, "DIM1/region");
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Don't know where to find dimension " + dimension);
|
||||
}
|
||||
final Pattern regionFilePattern = (version == VERSION_MCREGION)
|
||||
? Pattern.compile("r\\.-?\\d+\\.-?\\d+\\.mcr")
|
||||
: Pattern.compile("r\\.-?\\d+\\.-?\\d+\\.mca");
|
||||
final File[] regionFiles = regionDir.listFiles((dir, name) -> regionFilePattern.matcher(name).matches());
|
||||
for (File file: regionFiles) {
|
||||
try (RegionFile regionFile = new RegionFile(file, true)) {
|
||||
for (int x = 0; x < 32; x++) {
|
||||
for (int z = 0; z < 32; z++) {
|
||||
if (regionFile.containsChunk(x, z)) {
|
||||
final Tag tag;
|
||||
final InputStream chunkData = regionFile.getChunkDataInputStream(x, z);
|
||||
try (NBTInputStream in = new NBTInputStream(chunkData)) {
|
||||
tag = in.readTag();
|
||||
} catch (RuntimeException e) {
|
||||
logger.error("{} while reading tag for chunk {},{}", e.getClass().getSimpleName(), x, z);
|
||||
e.printStackTrace();
|
||||
continue;
|
||||
}
|
||||
final Chunk chunk;
|
||||
try {
|
||||
chunk = (version == VERSION_MCREGION)
|
||||
? new MCRegionChunk((CompoundTag) tag, maxHeight)
|
||||
: ((dataVersion > DATA_VERSION_MC_1_12_2)
|
||||
? new MC113AnvilChunk((CompoundTag) tag, maxHeight)
|
||||
: new MC12AnvilChunk((CompoundTag) tag, maxHeight));
|
||||
} catch (RuntimeException e) {
|
||||
logger.error("{} while parsing tag for chunk {},{}", e.getClass().getSimpleName(), x, z);
|
||||
e.printStackTrace();
|
||||
logger.error(tag.toString());
|
||||
continue;
|
||||
}
|
||||
visitor.visitChunk(chunk);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Dimension " + dimension + " not supported");
|
||||
}
|
||||
return regionDir;
|
||||
}
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(MinecraftUtil.class);
|
||||
|
|
|
@ -100,6 +100,11 @@
|
|||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="checkBoxImportEnd" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<Component id="jLabel6" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="labelPlatform" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
<EmptySpace min="0" pref="0" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
|
@ -118,7 +123,12 @@
|
|||
<Component id="fieldFilename" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="buttonSelectFile" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace type="unrelated" max="-2" attributes="0"/>
|
||||
<EmptySpace type="separate" max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="3" attributes="0">
|
||||
<Component id="jLabel6" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="labelPlatform" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace type="separate" max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="3" attributes="0">
|
||||
<Component id="jLabel2" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="labelOutliers1" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
|
@ -340,5 +350,12 @@
|
|||
<Property name="enabled" type="boolean" value="false"/>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="jLabel6">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" value="Map format:"/>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="labelPlatform">
|
||||
</Component>
|
||||
</SubComponents>
|
||||
</Form>
|
||||
|
|
|
@ -4,14 +4,16 @@
|
|||
*/
|
||||
package org.pepsoft.worldpainter.importing;
|
||||
|
||||
import org.pepsoft.minecraft.ChunkStore;
|
||||
import org.pepsoft.minecraft.Level;
|
||||
import org.pepsoft.minecraft.RegionFile;
|
||||
import org.pepsoft.util.FileUtils;
|
||||
import org.pepsoft.util.ProgressReceiver;
|
||||
import org.pepsoft.util.ProgressReceiver.OperationCancelled;
|
||||
import org.pepsoft.util.swing.ProgressDialog;
|
||||
import org.pepsoft.util.swing.ProgressTask;
|
||||
import org.pepsoft.worldpainter.*;
|
||||
import org.pepsoft.worldpainter.plugins.BlockBasedPlatformProvider;
|
||||
import org.pepsoft.worldpainter.plugins.PlatformManager;
|
||||
import org.pepsoft.worldpainter.util.MinecraftUtil;
|
||||
|
||||
import javax.swing.*;
|
||||
|
@ -23,12 +25,17 @@ import java.io.File;
|
|||
import java.io.IOException;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.text.NumberFormat;
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.*;
|
||||
|
||||
import static java.util.Arrays.stream;
|
||||
import static java.util.stream.Collectors.toSet;
|
||||
import static javax.swing.JOptionPane.ERROR_MESSAGE;
|
||||
import static org.pepsoft.minecraft.Constants.VERSION_ANVIL;
|
||||
import static org.pepsoft.minecraft.Constants.VERSION_MCREGION;
|
||||
import static org.pepsoft.worldpainter.Constants.*;
|
||||
import static org.pepsoft.worldpainter.DefaultPlugin.JAVA_ANVIL;
|
||||
import static org.pepsoft.worldpainter.DefaultPlugin.JAVA_MCREGION;
|
||||
|
||||
/**
|
||||
*
|
||||
|
@ -87,7 +94,7 @@ public class MapImportDialog extends WorldPainterDialog {
|
|||
}
|
||||
|
||||
private void analyseMap() {
|
||||
mapStatistics = null;
|
||||
mapInfo = null;
|
||||
resetStats();
|
||||
|
||||
File levelDatFile = new File(fieldFilename.getText());
|
||||
|
@ -100,117 +107,100 @@ public class MapImportDialog extends WorldPainterDialog {
|
|||
version = testLevel.getVersion();
|
||||
} catch (IOException e) {
|
||||
logger.error("IOException while analysing map " + levelDatFile, e);
|
||||
JOptionPane.showMessageDialog(MapImportDialog.this, strings.getString("selected.file.is.not.a.valid.level.dat.file"), strings.getString("invalid.file"), JOptionPane.ERROR_MESSAGE);
|
||||
JOptionPane.showMessageDialog(MapImportDialog.this, strings.getString("selected.file.is.not.a.valid.level.dat.file"), strings.getString("invalid.file"), ERROR_MESSAGE);
|
||||
return;
|
||||
} catch (IllegalArgumentException e) {
|
||||
logger.error("IllegalArgumentException while analysing map " + levelDatFile, e);
|
||||
JOptionPane.showMessageDialog(MapImportDialog.this, strings.getString("selected.file.is.not.a.valid.level.dat.file"), strings.getString("invalid.file"), JOptionPane.ERROR_MESSAGE);
|
||||
JOptionPane.showMessageDialog(MapImportDialog.this, strings.getString("selected.file.is.not.a.valid.level.dat.file"), strings.getString("invalid.file"), ERROR_MESSAGE);
|
||||
return;
|
||||
} catch (NullPointerException e) {
|
||||
logger.error("NullPointerException while analysing map " + levelDatFile, e);
|
||||
JOptionPane.showMessageDialog(MapImportDialog.this, strings.getString("selected.file.is.not.a.valid.level.dat.file"), strings.getString("invalid.file"), JOptionPane.ERROR_MESSAGE);
|
||||
JOptionPane.showMessageDialog(MapImportDialog.this, strings.getString("selected.file.is.not.a.valid.level.dat.file"), strings.getString("invalid.file"), ERROR_MESSAGE);
|
||||
return;
|
||||
}
|
||||
|
||||
// Other sanity checks
|
||||
if ((version != VERSION_MCREGION) && (version != VERSION_ANVIL)) {
|
||||
logger.error("Unsupported Minecraft version while analysing map " + levelDatFile);
|
||||
JOptionPane.showMessageDialog(MapImportDialog.this, strings.getString("unsupported.minecraft.version"), strings.getString("unsupported.version"), JOptionPane.ERROR_MESSAGE);
|
||||
JOptionPane.showMessageDialog(MapImportDialog.this, strings.getString("unsupported.minecraft.version"), strings.getString("unsupported.version"), ERROR_MESSAGE);
|
||||
return;
|
||||
}
|
||||
File regionDir = new File(worldDir, "region");
|
||||
if (! regionDir.isDirectory()) {
|
||||
logger.error("Region directory missing while analysing map " + levelDatFile);
|
||||
JOptionPane.showMessageDialog(MapImportDialog.this, strings.getString("the.region.folder.is.missing"), strings.getString("region.folder.missing"), JOptionPane.ERROR_MESSAGE);
|
||||
|
||||
// Determine the platform
|
||||
Platform platform;
|
||||
// TODO handle non-block based platform provider matching more gracefully
|
||||
BlockBasedPlatformProvider platformProvider = (BlockBasedPlatformProvider) PlatformManager.getInstance().identifyMap(worldDir);
|
||||
// TODO migrate this so that the map recognizer returns the actual platform and this ugly business isn't needed
|
||||
if (platformProvider.getName().equals("DefaultPlatforms")) {
|
||||
platform = (version == SUPPORTED_VERSION_1) ? JAVA_MCREGION : JAVA_ANVIL;
|
||||
} else {
|
||||
Collection<Platform> platforms = platformProvider.getKeys();
|
||||
if (platforms.size() == 1) {
|
||||
platform = platforms.iterator().next();
|
||||
} else {
|
||||
logger.error("Could not determine single matching platform for " + levelDatFile + "(matching platforms: " + platforms + ")");
|
||||
JOptionPane.showMessageDialog(MapImportDialog.this, "Could not determine single matching platform for " + levelDatFile + "(matching platforms: " + platforms + ")", "Multiple Matching Platforms", ERROR_MESSAGE);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Sanity checks for the surface dimension
|
||||
Set<Integer> dimensions = stream(platformProvider.getDimensions(platform, worldDir)).boxed().collect(toSet());
|
||||
if (! dimensions.contains(DIM_NORMAL)) {
|
||||
logger.error("Map has no surface dimension: " + levelDatFile);
|
||||
JOptionPane.showMessageDialog(MapImportDialog.this, "This map has no surface dimension; this is not supported by WorldPainter", "Missing Surface Dimension", ERROR_MESSAGE);
|
||||
return;
|
||||
}
|
||||
final Pattern regionFilePattern = (version == VERSION_MCREGION)
|
||||
? Pattern.compile("r\\.-?\\d+\\.-?\\d+\\.mcr")
|
||||
: Pattern.compile("r\\.-?\\d+\\.-?\\d+\\.mca");
|
||||
final File[] regionFiles = regionDir.listFiles((dir, name) -> regionFilePattern.matcher(name).matches());
|
||||
if ((regionFiles == null) || (regionFiles.length == 0)) {
|
||||
logger.error("Region files missing while analysing map " + levelDatFile);
|
||||
JOptionPane.showMessageDialog(MapImportDialog.this, strings.getString("the.region.folder.contains.no.region.files"), strings.getString("region.files.missing"), JOptionPane.ERROR_MESSAGE);
|
||||
final ChunkStore surfaceChunkStore = platformProvider.getChunkStore(platform, worldDir, DIM_NORMAL);
|
||||
final long surfaceChunkCount = surfaceChunkStore.getChunkCount();
|
||||
if (surfaceChunkCount == 0) {
|
||||
logger.error("Surface dimension does not contain any chunks: " + levelDatFile);
|
||||
JOptionPane.showMessageDialog(MapImportDialog.this, "This map has an empty surface dimension; this is not supported by WorldPainter", "Empty Surface Dimension", ERROR_MESSAGE);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Check for Nether and End
|
||||
boolean netherPresent = false, endPresent = false;
|
||||
File netherRegionDir = new File(worldDir, "DIM-1/region");
|
||||
if (netherRegionDir.isDirectory()) {
|
||||
File[] netherRegionFiles = netherRegionDir.listFiles((dir, name) -> regionFilePattern.matcher(name).matches());
|
||||
if ((netherRegionFiles != null) && (netherRegionFiles.length > 0)) {
|
||||
netherPresent = true;
|
||||
}
|
||||
}
|
||||
File endRegionDir = new File(worldDir, "DIM1/region");
|
||||
if (endRegionDir.isDirectory()) {
|
||||
File[] endRegionFiles = endRegionDir.listFiles((dir, name) -> regionFilePattern.matcher(name).matches());
|
||||
if ((endRegionFiles != null) && (endRegionFiles.length > 0)) {
|
||||
endPresent = true;
|
||||
}
|
||||
}
|
||||
final boolean netherPresent = dimensions.contains(DIM_NETHER), endPresent = dimensions.contains(DIM_END);
|
||||
checkBoxImportNether.setEnabled(netherPresent);
|
||||
checkBoxImportNether.setSelected(netherPresent);
|
||||
checkBoxImportEnd.setEnabled(endPresent);
|
||||
checkBoxImportEnd.setSelected(endPresent);
|
||||
|
||||
mapStatistics = ProgressDialog.executeTask(this, new ProgressTask<MapStatistics>() {
|
||||
mapInfo = ProgressDialog.executeTask(this, new ProgressTask<MapInfo>() {
|
||||
@Override
|
||||
public String getName() {
|
||||
return "Analyzing map...";
|
||||
}
|
||||
|
||||
@Override
|
||||
public MapStatistics execute(ProgressReceiver progressReceiver) throws OperationCancelled {
|
||||
MapStatistics stats = new MapStatistics();
|
||||
|
||||
int chunkCount = 0;
|
||||
List<Integer> xValues = new ArrayList<>(), zValues = new ArrayList<>();
|
||||
List<Point> chunks = new ArrayList<>();
|
||||
int count = 0;
|
||||
for (File file: regionFiles) {
|
||||
String[] nameFrags = file.getName().split("\\.");
|
||||
int regionX = Integer.parseInt(nameFrags[1]);
|
||||
int regionZ = Integer.parseInt(nameFrags[2]);
|
||||
try {
|
||||
RegionFile regionFile = new RegionFile(file);
|
||||
try {
|
||||
for (int x = 0; x < 32; x++) {
|
||||
for (int z = 0; z < 32; z++) {
|
||||
if (regionFile.containsChunk(x, z)) {
|
||||
chunkCount++;
|
||||
int chunkX = regionX * 32 + x, chunkZ = regionZ * 32 + z;
|
||||
if (chunkX < stats.lowestChunkX) {
|
||||
stats.lowestChunkX = chunkX;
|
||||
}
|
||||
if (chunkX > stats.highestChunkX) {
|
||||
stats.highestChunkX = chunkX;
|
||||
}
|
||||
if (chunkZ < stats.lowestChunkZ) {
|
||||
stats.lowestChunkZ = chunkZ;
|
||||
}
|
||||
if (chunkZ > stats.highestChunkZ) {
|
||||
stats.highestChunkZ = chunkZ;
|
||||
}
|
||||
xValues.add(chunkX);
|
||||
zValues.add(chunkZ);
|
||||
chunks.add(new Point(chunkX, chunkZ));
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
regionFile.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("I/O error while analyzing map " + worldDir, e);
|
||||
}
|
||||
count++;
|
||||
progressReceiver.setProgress((float) count / (regionFiles.length + 1));
|
||||
}
|
||||
stats.chunkCount = chunkCount;
|
||||
public MapInfo execute(ProgressReceiver progressReceiver) throws OperationCancelled {
|
||||
final MapInfo stats = new MapInfo();
|
||||
stats.chunkCount = surfaceChunkCount;
|
||||
|
||||
if (chunkCount == 0) {
|
||||
// TODO do this for the other dimensions as well
|
||||
final List<Integer> xValues = new ArrayList<>(), zValues = new ArrayList<>();
|
||||
final List<Point> chunks = new ArrayList<>();
|
||||
surfaceChunkStore.visitChunks(chunk -> {
|
||||
final Point coords = chunk.getCoords();
|
||||
if (coords.x < stats.lowestChunkX) {
|
||||
stats.lowestChunkX = coords.x;
|
||||
}
|
||||
if (coords.x > stats.highestChunkX) {
|
||||
stats.highestChunkX = coords.x;
|
||||
}
|
||||
if (coords.y < stats.lowestChunkZ) {
|
||||
stats.lowestChunkZ = coords.y;
|
||||
}
|
||||
if (coords.y > stats.highestChunkZ) {
|
||||
stats.highestChunkZ = coords.y;
|
||||
}
|
||||
xValues.add(coords.x);
|
||||
zValues.add(coords.y);
|
||||
chunks.add(coords);
|
||||
return true;
|
||||
});
|
||||
|
||||
if (stats.chunkCount == 0) {
|
||||
// Completely empty map (wrong region file format)?
|
||||
progressReceiver.setProgress(1.0f);
|
||||
return stats;
|
||||
|
@ -274,18 +264,20 @@ public class MapImportDialog extends WorldPainterDialog {
|
|||
return stats;
|
||||
}
|
||||
});
|
||||
if ((mapStatistics != null) && (mapStatistics.chunkCount > 0)) {
|
||||
int width = mapStatistics.highestChunkXNoOutliers - mapStatistics.lowestChunkXNoOutliers + 1;
|
||||
int length = mapStatistics.highestChunkZNoOutliers - mapStatistics.lowestChunkZNoOutliers + 1;
|
||||
int area = (mapStatistics.chunkCount - mapStatistics.outlyingChunks.size());
|
||||
labelWidth.setText(FORMATTER.format(width * 16) + " blocks (from " + FORMATTER.format(mapStatistics.lowestChunkXNoOutliers << 4) + " to " + FORMATTER.format((mapStatistics.highestChunkXNoOutliers << 4) + 15) + "; " + FORMATTER.format(width) + " chunks)");
|
||||
labelLength.setText(FORMATTER.format(length * 16) + " blocks (from " + FORMATTER.format(mapStatistics.lowestChunkZNoOutliers << 4) + " to " + FORMATTER.format((mapStatistics.highestChunkZNoOutliers << 4) + 15) + "; " + FORMATTER.format(length) + " chunks)");
|
||||
if ((mapInfo != null) && (mapInfo.chunkCount > 0)) {
|
||||
mapInfo.platform = platform;
|
||||
labelPlatform.setText(platform.displayName);
|
||||
int width = mapInfo.highestChunkXNoOutliers - mapInfo.lowestChunkXNoOutliers + 1;
|
||||
int length = mapInfo.highestChunkZNoOutliers - mapInfo.lowestChunkZNoOutliers + 1;
|
||||
long area = (mapInfo.chunkCount - mapInfo.outlyingChunks.size());
|
||||
labelWidth.setText(FORMATTER.format(width * 16) + " blocks (from " + FORMATTER.format(mapInfo.lowestChunkXNoOutliers << 4) + " to " + FORMATTER.format((mapInfo.highestChunkXNoOutliers << 4) + 15) + "; " + FORMATTER.format(width) + " chunks)");
|
||||
labelLength.setText(FORMATTER.format(length * 16) + " blocks (from " + FORMATTER.format(mapInfo.lowestChunkZNoOutliers << 4) + " to " + FORMATTER.format((mapInfo.highestChunkZNoOutliers << 4) + 15) + "; " + FORMATTER.format(length) + " chunks)");
|
||||
labelArea.setText(FORMATTER.format(area * 256L) + " blocks² (" + FORMATTER.format(area) + " chunks)");
|
||||
if (! mapStatistics.outlyingChunks.isEmpty()) {
|
||||
if (! mapInfo.outlyingChunks.isEmpty()) {
|
||||
// There are outlying chunks
|
||||
int widthWithOutliers = mapStatistics.highestChunkX - mapStatistics.lowestChunkX + 1;
|
||||
int lengthWithOutliers = mapStatistics.highestChunkZ - mapStatistics.lowestChunkZ + 1;
|
||||
int areaOfOutliers = mapStatistics.outlyingChunks.size();
|
||||
int widthWithOutliers = mapInfo.highestChunkX - mapInfo.lowestChunkX + 1;
|
||||
int lengthWithOutliers = mapInfo.highestChunkZ - mapInfo.lowestChunkZ + 1;
|
||||
int areaOfOutliers = mapInfo.outlyingChunks.size();
|
||||
labelOutliers1.setVisible(true);
|
||||
labelOutliers2.setVisible(true);
|
||||
labelWidthWithOutliers.setText(FORMATTER.format(widthWithOutliers * 16) + " blocks (" + FORMATTER.format(widthWithOutliers) + " chunks)");
|
||||
|
@ -306,7 +298,7 @@ public class MapImportDialog extends WorldPainterDialog {
|
|||
private void setControlStates() {
|
||||
String fileStr = fieldFilename.getText().trim();
|
||||
File file = (! fileStr.isEmpty()) ? new File(fileStr) : null;
|
||||
if ((mapStatistics == null) || (mapStatistics.chunkCount == 0) || (file == null) || (! file.isFile())) {
|
||||
if ((mapInfo == null) || (mapInfo.chunkCount == 0) || (file == null) || (! file.isFile())) {
|
||||
buttonOK.setEnabled(false);
|
||||
} else {
|
||||
buttonOK.setEnabled(true);
|
||||
|
@ -352,7 +344,7 @@ public class MapImportDialog extends WorldPainterDialog {
|
|||
|
||||
private void importWorld() {
|
||||
final File levelDatFile = new File(fieldFilename.getText());
|
||||
final Set<Point> chunksToSkip = checkBoxImportOutliers.isSelected() ? null : mapStatistics.outlyingChunks;
|
||||
final Set<Point> chunksToSkip = checkBoxImportOutliers.isSelected() ? null : mapInfo.outlyingChunks;
|
||||
final MapImporter.ReadOnlyOption readOnlyOption;
|
||||
if (radioButtonReadOnlyAll.isSelected()) {
|
||||
readOnlyOption = MapImporter.ReadOnlyOption.ALL;
|
||||
|
@ -384,14 +376,14 @@ public class MapImportDialog extends WorldPainterDialog {
|
|||
int terrainLevel = waterLevel - 4;
|
||||
TileFactory tileFactory = TileFactoryFactory.createNoiseTileFactory(0, Terrain.GRASS, maxHeight, terrainLevel, waterLevel, false, true, 20, 1.0);
|
||||
Set<Integer> dimensionsToImport = new HashSet<>(3);
|
||||
dimensionsToImport.add(Constants.DIM_NORMAL);
|
||||
dimensionsToImport.add(DIM_NORMAL);
|
||||
if (checkBoxImportNether.isSelected()) {
|
||||
dimensionsToImport.add(Constants.DIM_NETHER);
|
||||
}
|
||||
if (checkBoxImportEnd.isSelected()) {
|
||||
dimensionsToImport.add(Constants.DIM_END);
|
||||
}
|
||||
final MapImporter importer = new JavaMapImporter(tileFactory, levelDatFile, false, chunksToSkip, readOnlyOption, dimensionsToImport);
|
||||
final MapImporter importer = new JavaMapImporter(mapInfo.platform, tileFactory, levelDatFile, false, chunksToSkip, readOnlyOption, dimensionsToImport);
|
||||
World2 world = importer.doImport(progressReceiver);
|
||||
if (importer.getWarnings() != null) {
|
||||
try {
|
||||
|
@ -465,6 +457,8 @@ public class MapImportDialog extends WorldPainterDialog {
|
|||
checkBoxImportSurface = new javax.swing.JCheckBox();
|
||||
checkBoxImportNether = new javax.swing.JCheckBox();
|
||||
checkBoxImportEnd = new javax.swing.JCheckBox();
|
||||
jLabel6 = new javax.swing.JLabel();
|
||||
labelPlatform = new javax.swing.JLabel();
|
||||
|
||||
setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE);
|
||||
setTitle("Import Existing Minecraft Map");
|
||||
|
@ -549,6 +543,8 @@ public class MapImportDialog extends WorldPainterDialog {
|
|||
checkBoxImportEnd.setText("Import End");
|
||||
checkBoxImportEnd.setEnabled(false);
|
||||
|
||||
jLabel6.setText("Map format:");
|
||||
|
||||
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
|
||||
getContentPane().setLayout(layout);
|
||||
layout.setHorizontalGroup(
|
||||
|
@ -611,7 +607,11 @@ public class MapImportDialog extends WorldPainterDialog {
|
|||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(checkBoxImportNether)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(checkBoxImportEnd)))
|
||||
.addComponent(checkBoxImportEnd))
|
||||
.addGroup(layout.createSequentialGroup()
|
||||
.addComponent(jLabel6)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(labelPlatform)))
|
||||
.addGap(0, 0, Short.MAX_VALUE)))
|
||||
.addContainerGap())
|
||||
);
|
||||
|
@ -624,7 +624,11 @@ public class MapImportDialog extends WorldPainterDialog {
|
|||
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
|
||||
.addComponent(fieldFilename, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addComponent(buttonSelectFile))
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
|
||||
.addGap(18, 18, 18)
|
||||
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
|
||||
.addComponent(jLabel6)
|
||||
.addComponent(labelPlatform))
|
||||
.addGap(18, 18, 18)
|
||||
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
|
||||
.addComponent(jLabel2)
|
||||
.addComponent(labelOutliers1))
|
||||
|
@ -700,6 +704,7 @@ public class MapImportDialog extends WorldPainterDialog {
|
|||
private javax.swing.JLabel jLabel3;
|
||||
private javax.swing.JLabel jLabel4;
|
||||
private javax.swing.JLabel jLabel5;
|
||||
private javax.swing.JLabel jLabel6;
|
||||
private javax.swing.JLabel jLabel7;
|
||||
private javax.swing.JLabel labelArea;
|
||||
private javax.swing.JLabel labelAreaOutliers;
|
||||
|
@ -709,6 +714,7 @@ public class MapImportDialog extends WorldPainterDialog {
|
|||
private javax.swing.JLabel labelOutliers2;
|
||||
private javax.swing.JLabel labelOutliers3;
|
||||
private javax.swing.JLabel labelOutliers4;
|
||||
private javax.swing.JLabel labelPlatform;
|
||||
private javax.swing.JLabel labelWidth;
|
||||
private javax.swing.JLabel labelWidthWithOutliers;
|
||||
private javax.swing.JRadioButton radioButtonReadOnlyAll;
|
||||
|
@ -719,7 +725,7 @@ public class MapImportDialog extends WorldPainterDialog {
|
|||
|
||||
private final App app;
|
||||
private File previouslySelectedFile;
|
||||
private MapStatistics mapStatistics;
|
||||
private MapInfo mapInfo;
|
||||
private World2 importedWorld;
|
||||
|
||||
private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(MapImportDialog.class);
|
||||
|
@ -727,10 +733,11 @@ public class MapImportDialog extends WorldPainterDialog {
|
|||
private static final NumberFormat FORMATTER = NumberFormat.getIntegerInstance();
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
static class MapStatistics {
|
||||
static class MapInfo {
|
||||
Platform platform;
|
||||
int lowestChunkX = Integer.MAX_VALUE, lowestChunkZ = Integer.MAX_VALUE, highestChunkX = Integer.MIN_VALUE, highestChunkZ = Integer.MIN_VALUE;
|
||||
int lowestChunkXNoOutliers = Integer.MAX_VALUE, lowestChunkZNoOutliers = Integer.MAX_VALUE, highestChunkXNoOutliers = Integer.MIN_VALUE, highestChunkZNoOutliers = Integer.MIN_VALUE;
|
||||
int chunkCount;
|
||||
long chunkCount;
|
||||
final Set<Point> outlyingChunks = new HashSet<>();
|
||||
String errorMessage;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue