package org.pepsoft.worldpainter;
import org.jnbt.CompoundTag;
import org.jnbt.NBTInputStream;
import org.jnbt.Tag;
import org.junit.Test;
import org.pepsoft.minecraft.*;
import org.pepsoft.util.FileUtils;
import org.pepsoft.util.ProgressReceiver;
import org.pepsoft.worldpainter.exporting.WorldExporter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.awt.*;
import java.io.File;
import java.io.IOException;
import java.util.BitSet;
import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.pepsoft.minecraft.Constants.*;
import static org.pepsoft.worldpainter.Constants.*;
/**
* Created by Pepijn Schmitz on 09-01-17.
*/
@SuppressWarnings({"ResultOfMethodCallIgnored", "SameParameterValue"})
public class RegressionIT {
@Test
public void test2_3_6World() throws IOException, UnloadableWorldException, ProgressReceiver.OperationCancelled {
World2 world = loadWorld("/testset/test-v2.3.6-1.world");
File tmpBaseDir = createTmpBaseDir();
try {
File worldDir = exportWorld(world, tmpBaseDir);
verifyJavaWorld(worldDir, SUPPORTED_VERSION_2);
verifyJavaDimension(worldDir, world.getDimension(DIM_NORMAL),
// Bedrock
BLK_BEDROCK,
// Stone Mix underground material
BLK_STONE, BLK_GRAVEL, BLK_DIRT,
// Surface
BLK_GRASS, BLK_TALL_GRASS,
// Air
BLK_AIR,
// Lakes
BLK_STATIONARY_WATER,
// Resources layer
BLK_WATER, BLK_LAVA,
// Various tree layers
BLK_WOOD, BLK_LEAVES, BLK_VINES,
// Desert terrain type
BLK_SAND, BLK_CACTUS,
// Frost layer
BLK_SNOW,
// Netherlike terrain type
BLK_NETHERRACK, BLK_FIRE,
// Annotations
BLK_WOOL,
// Plants
BLK_LARGE_FLOWERS, BLK_WHEAT, BLK_CARROTS, BLK_POTATOES, BLK_PUMPKIN_STEM, BLK_MELON_STEM,
// Deep Snow layer
BLK_SNOW_BLOCK);
verifyJavaDimension(worldDir, world.getDimension(DIM_NETHER),
// Nether
BLK_NETHERRACK, BLK_SOUL_SAND, BLK_GLOWSTONE, BLK_FIRE, BLK_LAVA, BLK_AIR);
verifyJavaDimension(worldDir, world.getDimension(DIM_END),
// End
BLK_END_STONE, BLK_AIR);
} finally {
FileUtils.deleteDir(tmpBaseDir);
}
}
private World2 loadWorld(String worldName) throws IOException, UnloadableWorldException {
// Load the world
logger.info("Loading world {}", worldName);
WorldIO worldIO = new WorldIO();
worldIO.load(RegressionIT.class.getResourceAsStream(worldName));
return worldIO.getWorld();
}
private File createTmpBaseDir() {
File tmpBaseDir = new File(System.getProperty("java.io.tmpdir"), UUID.randomUUID().toString());
tmpBaseDir.mkdirs();
return tmpBaseDir;
}
private File exportWorld(World2 world, File baseDir) throws IOException, UnloadableWorldException, ProgressReceiver.OperationCancelled {
// Prepare for export
if (world.getVersion() == 0) {
world.setVersion((world.getMaxHeight() == DEFAULT_MAX_HEIGHT_2) ? SUPPORTED_VERSION_2 : SUPPORTED_VERSION_1);
}
for (int i = 0; i < Terrain.CUSTOM_TERRAIN_COUNT; i++) {
MixedMaterial material = world.getMixedMaterial(i);
Terrain.setCustomMaterial(i, material);
}
// Export
logger.info("Exporting world {}", world.getName());
WorldExporter worldExporter = new WorldExporter(world);
worldExporter.export(baseDir, world.getName(), null, null);
// Return the directory into which the world was exported
return new File(baseDir, FileUtils.sanitiseName(world.getName()));
}
private void verifyJavaWorld(File worldDir, int expectedVersion) throws IOException {
Level level = Level.load(new File(worldDir, "level.dat"));
assertEquals(expectedVersion, level.getVersion());
}
private void verifyJavaDimension(File worldDir, Dimension dimension, int... expectedBlocks) throws IOException {
World2 world = dimension.getWorld();
logger.info("Verifying dimension {} of map {}", dimension.getName(), world.getName());
int expectedLowestChunkX = dimension.getLowestX() << 3, expectedHighestChunkX = ((dimension.getHighestX() + 1) << 3) - 1;
int expectedLowestChunkZ = dimension.getLowestY() << 3, expectedHighestChunkZ = ((dimension.getHighestY() + 1) << 3) - 1;
Rectangle expectedBounds = new Rectangle(expectedLowestChunkX, expectedLowestChunkZ, expectedHighestChunkX - expectedLowestChunkX + 1, expectedHighestChunkZ - expectedLowestChunkZ + 1);
File regionDir;
switch (dimension.getDim()) {
case DIM_NORMAL:
regionDir = new File(worldDir, "region");
break;
case DIM_NETHER:
regionDir = new File(worldDir, "DIM-1/region");
break;
case DIM_END:
regionDir = new File(worldDir, "DIM1/region");
break;
default:
throw new IllegalArgumentException();
}
int version = world.getVersion();
int maxHeight = dimension.getMaxHeight();
Pattern regionFilePattern = (version == SUPPORTED_VERSION_1)
? Pattern.compile("r\\.(-?\\d+)\\.(-?\\d+)\\.mcr")
: Pattern.compile("r\\.(-?\\d+)\\.(-?\\d+)\\.mca");
int lowestChunkX = Integer.MAX_VALUE, highestChunkX = Integer.MIN_VALUE;
int lowestChunkZ = Integer.MAX_VALUE, highestChunkZ = Integer.MIN_VALUE;
BitSet blockTypes = new BitSet(256);
for (File file: regionDir.listFiles()) {
Matcher matcher = regionFilePattern.matcher(file.getName());
if (matcher.matches()) {
int regionX = Integer.parseInt(matcher.group(1));
int regionZ = Integer.parseInt(matcher.group(2));
try (RegionFile regionFile = new RegionFile(file, true)) {
for (int chunkX = 0; chunkX < 32; chunkX++) {
for (int chunkZ = 0; chunkZ < 32; chunkZ++) {
if (regionFile.containsChunk(chunkX, chunkZ)) {
int absChunkX = (regionX << 5) + chunkX;
int absChunkZ = (regionZ << 5) + chunkZ;
if (absChunkX < lowestChunkX) {
lowestChunkX = absChunkX;
}
if (absChunkX > highestChunkX) {
highestChunkX = absChunkX;
}
if (absChunkZ < lowestChunkZ) {
lowestChunkZ = absChunkZ;
}
if (absChunkZ > highestChunkZ) {
highestChunkZ = absChunkZ;
}
Chunk chunk;
try (NBTInputStream in = new NBTInputStream(regionFile.getChunkDataInputStream(chunkX, chunkZ))) {
Tag tag = in.readTag();
chunk = (version == SUPPORTED_VERSION_1)
? new ChunkImpl((CompoundTag) tag, maxHeight, true)
: new ChunkImpl2((CompoundTag) tag, maxHeight, true);
}
// Iterate over all blocks to check whether the
// basic data structure are present, and inventory
// all block types present
for (int x = 0; x < 16; x++) {
for (int y = 0; y < maxHeight; y++) {
for (int z = 0; z < 16; z++) {
blockTypes.set(chunk.getBlockType(x, y, z));
}
}
}
}
}
}
}
}
}
assertEquals(expectedBounds, new Rectangle(lowestChunkX, lowestChunkZ, highestChunkX - lowestChunkX + 1, highestChunkZ - lowestChunkZ + 1));
// Check blocks we know should definitely be present due to the terrain
// types and layers used
for (int expectedBlock: expectedBlocks) {
assertTrue("expected block type " + Block.BLOCKS[expectedBlock].name + " missing", blockTypes.get(expectedBlock));
}
}
private static final Logger logger = LoggerFactory.getLogger(RegressionIT.class);
}