/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package org.pepsoft.worldpainter.importing;
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;
import org.pepsoft.worldpainter.*;
import org.pepsoft.worldpainter.Dimension;
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.themes.SimpleTheme;
import org.pepsoft.worldpainter.vo.EventVO;
import java.awt.*;
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.Constants.*;
import static org.pepsoft.worldpainter.Constants.*;
/**
*
* @author pepijn
*/
public class MapImporter {
public MapImporter(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.tileFactory = tileFactory;
this.levelDatFile = levelDatFile;
this.populateNewChunks = populateNewChunks;
this.chunksToSkip = chunksToSkip;
this.readOnlyOption = readOnlyOption;
this.dimensionsToImport = dimensionsToImport;
}
public World2 doImport() throws IOException {
try {
return doImport(null);
} catch (ProgressReceiver.OperationCancelled e) {
throw new InternalError();
}
}
public World2 doImport(ProgressReceiver progressReceiver) throws IOException, ProgressReceiver.OperationCancelled {
long start = System.currentTimeMillis();
logger.info("Importing map from " + levelDatFile.getAbsolutePath());
Level level = Level.load(levelDatFile);
int version = level.getVersion();
if ((version != SUPPORTED_VERSION_1) && (version != SUPPORTED_VERSION_2)) {
throw new UnsupportedOperationException("Level format version " + version + " not supported");
}
String name = level.getName().trim();
int maxHeight = level.getMaxHeight();
World2 world = new World2(maxHeight);
world.addHistoryEntry(HistoryEntry.WORLD_IMPORTED_FROM_MINECRAFT_MAP, level.getName(), levelDatFile.getParentFile());
world.setCreateGoodiesChest(false);
world.setName(name);
world.setSpawnPoint(new Point(level.getSpawnX(), level.getSpawnZ()));
world.setImportedFrom(levelDatFile);
world.setMapFeatures(level.isMapFeatures());
if (level.isHardcore()) {
world.setGameType(World2.GAME_TYPE_HARDCORE);
} else {
world.setGameType(level.getGameType());
}
world.setGenerator(level.getGenerator());
world.setGeneratorOptions(level.getGeneratorOptions());
world.setVersion(version);
world.setDifficulty(level.getDifficulty());
if ((version == SUPPORTED_VERSION_2) && (level.getBorderSize() > 0.0)) {
// If the world is version 0x4abd and actually has border settings,
// load them
world.getBorderSettings().setCentreX((int) (level.getBorderCenterX() + 0.5));
world.getBorderSettings().setCentreY((int) (level.getBorderCenterZ() + 0.5));
world.getBorderSettings().setSize((int) (level.getBorderSize() + 0.5));
world.getBorderSettings().setSafeZone((int) (level.getBorderSafeZone() + 0.5));
world.getBorderSettings().setWarningBlocks((int) (level.getBorderWarningBlocks()+ 0.5));
world.getBorderSettings().setWarningTime((int) (level.getBorderWarningTime() + 0.5));
world.getBorderSettings().setSizeLerpTarget((int) (level.getBorderSizeLerpTarget() + 0.5));
world.getBorderSettings().setSizeLerpTime((int) level.getBorderSizeLerpTime());
world.getBorderSettings().setDamagePerBlock((int) (level.getBorderDamagePerBlock() + 0.5));
}
File worldDir = levelDatFile.getParentFile();
File regionDir = new File(worldDir, "region");
File netherDir = new File(worldDir, "DIM-1/region");
File endDir = new File(worldDir, "DIM1/region");
int dimCount = 1;
if (netherDir.isDirectory() && dimensionsToImport.contains(DIM_NETHER)) {
dimCount++;
}
if (endDir.isDirectory() && dimensionsToImport.contains(DIM_END)) {
dimCount++;
}
long minecraftSeed = level.getSeed();
tileFactory.setSeed(minecraftSeed);
Dimension dimension = new Dimension(minecraftSeed, tileFactory, DIM_NORMAL, maxHeight);
dimension.setEventsInhibited(true);
try {
dimension.setCoverSteepTerrain(false);
dimension.setSubsurfaceMaterial(Terrain.STONE);
dimension.setBorderLevel(62);
// Turn off smooth snow
FrostSettings frostSettings = new FrostSettings();
frostSettings.setMode(FrostSettings.MODE_FLAT);
dimension.setLayerSettings(Frost.INSTANCE, frostSettings);
ResourcesExporterSettings resourcesSettings = (ResourcesExporterSettings) dimension.getLayerSettings(Resources.INSTANCE);
resourcesSettings.setMinimumLevel(0);
if (version == SUPPORTED_VERSION_1) {
resourcesSettings.setChance(BLK_EMERALD_ORE, 0);
}
Configuration config = Configuration.getInstance();
dimension.setGridEnabled(config.isDefaultGridEnabled());
dimension.setGridSize(config.getDefaultGridSize());
dimension.setContoursEnabled(config.isDefaultContoursEnabled());
dimension.setContourSeparation(config.getDefaultContourSeparation());
String dimWarnings = importDimension(regionDir, dimension, version, (progressReceiver != null) ? new SubProgressReceiver(progressReceiver, 0.0f, 1.0f / dimCount) : null);
if (dimWarnings != null) {
if (warnings == null) {
warnings = dimWarnings;
} else {
warnings = warnings + dimWarnings;
}
}
} finally {
dimension.setEventsInhibited(false);
}
world.addDimension(dimension);
int dimNo = 1;
if (netherDir.isDirectory() && dimensionsToImport.contains(DIM_NETHER)) {
HeightMapTileFactory netherTileFactory = TileFactoryFactory.createNoiseTileFactory(minecraftSeed + 1, Terrain.NETHERRACK, maxHeight, 188, 192, true, false, 20f, 1.0);
SimpleTheme theme = (SimpleTheme) netherTileFactory.getTheme();
SortedMap<Integer, Terrain> terrainRanges = theme.getTerrainRanges();
terrainRanges.clear();
terrainRanges.put(-1, Terrain.NETHERRACK);
theme.setTerrainRanges(terrainRanges);
theme.setLayerMap(null);
dimension = new Dimension(minecraftSeed + 1, netherTileFactory, DIM_NETHER, maxHeight);
dimension.setEventsInhibited(true);
try {
dimension.setCoverSteepTerrain(false);
dimension.setSubsurfaceMaterial(Terrain.NETHERRACK);
ResourcesExporterSettings resourcesSettings = (ResourcesExporterSettings) dimension.getLayerSettings(Resources.INSTANCE);
resourcesSettings.setMinimumLevel(0);
if (version == SUPPORTED_VERSION_1) {
resourcesSettings.setChance(BLK_QUARTZ_ORE, 0);
}
String dimWarnings = importDimension(netherDir, dimension, version, (progressReceiver != null) ? new SubProgressReceiver(progressReceiver, (float) dimNo++ / dimCount, 1.0f / dimCount) : null);
if (dimWarnings != null) {
if (warnings == null) {
warnings = dimWarnings;
} else {
warnings = warnings + dimWarnings;
}
}
} finally {
dimension.setEventsInhibited(false);
}
world.addDimension(dimension);
}
if (endDir.isDirectory() && dimensionsToImport.contains(DIM_END)) {
HeightMapTileFactory endTileFactory = TileFactoryFactory.createNoiseTileFactory(minecraftSeed + 2, Terrain.END_STONE, maxHeight, 32, 0, false, false, 20f, 1.0);
SimpleTheme theme = (SimpleTheme) endTileFactory.getTheme();
SortedMap<Integer, Terrain> terrainRanges = theme.getTerrainRanges();
terrainRanges.clear();
terrainRanges.put(-1, Terrain.END_STONE);
theme.setTerrainRanges(terrainRanges);
theme.setLayerMap(Collections.emptyMap());
dimension = new Dimension(minecraftSeed + 2, endTileFactory, DIM_END, maxHeight);
dimension.setEventsInhibited(true);
try {
dimension.setCoverSteepTerrain(false);
dimension.setSubsurfaceMaterial(Terrain.END_STONE);
String dimWarnings = importDimension(endDir, dimension, version, (progressReceiver != null) ? new SubProgressReceiver(progressReceiver, (float) dimNo / dimCount, 1.0f / dimCount) : null);
if (dimWarnings != null) {
if (warnings == null) {
warnings = dimWarnings;
} else {
warnings = warnings + dimWarnings;
}
}
} finally {
dimension.setEventsInhibited(false);
}
world.addDimension(dimension);
}
// Log an event
Configuration config = Configuration.getInstance();
if (config != null) {
EventVO event = new EventVO(EVENT_KEY_ACTION_IMPORT_MAP).duration(System.currentTimeMillis() - start);
event.setAttribute(EventVO.ATTRIBUTE_TIMESTAMP, new Date(start));
event.setAttribute(ATTRIBUTE_KEY_MAX_HEIGHT, world.getMaxHeight());
event.setAttribute(ATTRIBUTE_KEY_VERSION, world.getVersion());
event.setAttribute(ATTRIBUTE_KEY_MAP_FEATURES, world.isMapFeatures());
event.setAttribute(ATTRIBUTE_KEY_GAME_TYPE, world.getGameType());
event.setAttribute(ATTRIBUTE_KEY_ALLOW_CHEATS, world.isAllowCheats());
event.setAttribute(ATTRIBUTE_KEY_GENERATOR, world.getGenerator().name());
if ((world.getVersion() == SUPPORTED_VERSION_2) && (world.getGenerator() == Generator.FLAT)) {
event.setAttribute(ATTRIBUTE_KEY_GENERATOR_OPTIONS, world.getGeneratorOptions());
}
event.setAttribute(ATTRIBUTE_KEY_TILES, dimension.getTiles().size());
config.logEvent(event);
}
return world;
}
public String getWarnings() {
return warnings;
}
private String importDimension(File regionDir, Dimension dimension, int version, ProgressReceiver progressReceiver) throws IOException, ProgressReceiver.OperationCancelled {
if (progressReceiver != null) {
progressReceiver.setMessage(dimension.getName() + " dimension");
}
final int maxHeight = dimension.getMaxHeight();
final int maxY = maxHeight - 1;
final Pattern regionFilePattern = (version == SUPPORTED_VERSION_1)
? 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)) {
throw new RuntimeException("The " + dimension.getName() + " dimension of this map has no region files!");
}
final Set<Point> newChunks = new HashSet<>();
// final SortedSet<Material> manMadeBlockTypes = new TreeSet<Material>();
final boolean importBiomes = (version == SUPPORTED_VERSION_2) && (dimension.getDim() == DIM_NORMAL);
final int total = regionFiles.length * 1024;
int count = 0;
final StringBuilder reportBuilder = new StringBuilder();
for (File file: regionFiles) {
try {
RegionFile regionFile = new RegionFile(file);
try {
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 Tag tag;
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)) {
tag = in.readTag();
}
} 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;
}
final Chunk chunk = (version == SUPPORTED_VERSION_1)
? new ChunkImpl((CompoundTag) tag, maxHeight)
: new ChunkImpl2((CompoundTag) tag, maxHeight);
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--) {
int blockType = chunk.getBlockType(xx, y, zz);
int data = chunk.getDataValue(xx, y, zz);
if (! NATURAL_BLOCKS.get(blockType)) {
if (height == -1.0f) {
manMadeStructuresAboveGround = true;
} else {
manMadeStructuresBelowGround = true;
}
// manMadeBlockTypes.add(Material.get(blockType, data));
}
if ((blockType == BLK_SNOW) || (blockType == BLK_ICE)) {
frost = true;
}
if (((blockType == BLK_ICE) || (blockType == BLK_FROSTED_ICE) || (((blockType == BLK_STATIONARY_WATER) || (blockType == BLK_WATER) || (blockType == BLK_STATIONARY_LAVA) || (blockType == BLK_LAVA)) && (data == 0))) && (waterLevel == 0)) {
waterLevel = y;
if ((blockType == BLK_LAVA) || (blockType == BLK_STATIONARY_LAVA)) {
floodWithLava = true;
}
} else if (height == -1.0f) {
final Material material = Material.get(blockType, data);
if (SPECIAL_TERRAIN_MAPPING.containsKey(material)) {
// Special terrain found
height = y - 0.4375f; // Value that falls in the middle of the lowest one eigthth which will still round to the same integer value and will receive a one layer thick smooth snow block (principle of least surprise)
terrain = SPECIAL_TERRAIN_MAPPING.get(material);
} else if (TERRAIN_MAPPING.containsKey(blockType)) {
// Terrain found
height = y - 0.4375f; // Value that falls in the middle of the lowest one eigthth 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(blockType);
}
}
}
// Use smooth snow, if present, to better approximate world height, so smooth snow will survive merge
final int intHeight = (int) (height + 0.5f);
if ((height != -1.0f) && (intHeight < maxY) && (chunk.getBlockType(xx, intHeight + 1, zz) == BLK_SNOW)) {
int data = chunk.getDataValue(xx, intHeight + 1, zz);
height += data * 0.125;
}
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 the biome is set (around the edges of the map Minecraft sets it to
// 255, presumably as a marker that it has yet to be calculated), copy
// it to the dimension. However, if it matches what the automatic biome
// would be, don't copy it, so that WorldPainter will automatically
// adjust the biome when the user makes changes
if ((biome != 255) && (biome != dimension.getAutoBiome(blockX, blockY))) {
dimension.setLayerValueAt(Biome.INSTANCE, blockX, blockY, biome);
}
}
}
}
} 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);
}
}
}
}
} finally {
regionFile.close();
}
} 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);
}
}
// Process chunks that were only added to fill out a tile
for (Point newChunkCoords: newChunks) {
dimension.setBitLayerValueAt(NotPresent.INSTANCE, newChunkCoords.x, newChunkCoords.y, true);
if (populateNewChunks) {
dimension.setBitLayerValueAt(Populate.INSTANCE, newChunkCoords.x, newChunkCoords.y, true);
}
}
if (progressReceiver != null) {
progressReceiver.setProgress(1.0f);
}
// System.err.println("Man-made block types encountered:");
// for (Material blockType: manMadeBlockTypes) {
// System.err.println(blockType);
// }
return reportBuilder.length() != 0 ? reportBuilder.toString() : null;
}
private final TileFactory tileFactory;
private final File levelDatFile;
private final boolean populateNewChunks;
private final Set<Point> chunksToSkip;
private final ReadOnlyOption readOnlyOption;
private final Set<Integer> dimensionsToImport;
private String warnings;
public static final Map<Integer, Terrain> TERRAIN_MAPPING = new HashMap<>();
public static final Map<Material, Terrain> SPECIAL_TERRAIN_MAPPING = new HashMap<>();
public static final BitSet NATURAL_BLOCKS = new BitSet();
private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(MapImporter.class);
private static final String EOL = System.getProperty("line.separator");
static {
TERRAIN_MAPPING.put(BLK_STONE, Terrain.STONE);
TERRAIN_MAPPING.put(BLK_GRASS, Terrain.BARE_GRASS);
TERRAIN_MAPPING.put(BLK_DIRT, Terrain.DIRT);
TERRAIN_MAPPING.put(BLK_BEDROCK, Terrain.BEDROCK);
TERRAIN_MAPPING.put(BLK_SAND, Terrain.SAND);
TERRAIN_MAPPING.put(BLK_GRAVEL, Terrain.GRAVEL);
TERRAIN_MAPPING.put(BLK_GOLD_ORE, Terrain.STONE);
TERRAIN_MAPPING.put(BLK_IRON_ORE, Terrain.STONE);
TERRAIN_MAPPING.put(BLK_COAL, Terrain.STONE);
TERRAIN_MAPPING.put(BLK_LAPIS_LAZULI_ORE, Terrain.STONE);
TERRAIN_MAPPING.put(BLK_DIAMOND_ORE, Terrain.STONE);
TERRAIN_MAPPING.put(BLK_REDSTONE_ORE, Terrain.STONE);
TERRAIN_MAPPING.put(BLK_GLOWING_REDSTONE_ORE, Terrain.STONE);
TERRAIN_MAPPING.put(BLK_HIDDEN_SILVERFISH, Terrain.STONE);
TERRAIN_MAPPING.put(BLK_SANDSTONE, Terrain.SANDSTONE);
TERRAIN_MAPPING.put(BLK_OBSIDIAN, Terrain.OBSIDIAN);
TERRAIN_MAPPING.put(BLK_TILLED_DIRT, Terrain.DIRT);
TERRAIN_MAPPING.put(BLK_SNOW_BLOCK, Terrain.DEEP_SNOW);
TERRAIN_MAPPING.put(BLK_CLAY, Terrain.CLAY);
TERRAIN_MAPPING.put(BLK_NETHERRACK, Terrain.NETHERRACK);
TERRAIN_MAPPING.put(BLK_QUARTZ_ORE, Terrain.NETHERRACK);
TERRAIN_MAPPING.put(BLK_SOUL_SAND, Terrain.SOUL_SAND);
TERRAIN_MAPPING.put(BLK_MYCELIUM, Terrain.MYCELIUM);
TERRAIN_MAPPING.put(BLK_END_STONE, Terrain.END_STONE);
TERRAIN_MAPPING.put(BLK_HARDENED_CLAY, Terrain.HARDENED_CLAY);
TERRAIN_MAPPING.put(BLK_RED_SANDSTONE, Terrain.RED_SANDSTONE);
TERRAIN_MAPPING.put(BLK_GRASS_PATH, Terrain.GRASS_PATH);
TERRAIN_MAPPING.put(BLK_MAGMA, Terrain.MAGMA); // TODO: or should this be mapped to stone and magma added to the Resources layer?
SPECIAL_TERRAIN_MAPPING.put(Material.RED_SAND, Terrain.RED_SAND);
SPECIAL_TERRAIN_MAPPING.put(Material.PERMADIRT, Terrain.PERMADIRT);
SPECIAL_TERRAIN_MAPPING.put(Material.PODZOL, Terrain.PODZOL);
SPECIAL_TERRAIN_MAPPING.put(Material.WHITE_CLAY, Terrain.WHITE_STAINED_CLAY);
SPECIAL_TERRAIN_MAPPING.put(Material.ORANGE_CLAY, Terrain.ORANGE_STAINED_CLAY);
SPECIAL_TERRAIN_MAPPING.put(Material.MAGENTA_CLAY, Terrain.MAGENTA_STAINED_CLAY);
SPECIAL_TERRAIN_MAPPING.put(Material.LIGHT_BLUE_CLAY, Terrain.LIGHT_BLUE_STAINED_CLAY);
SPECIAL_TERRAIN_MAPPING.put(Material.YELLOW_CLAY, Terrain.YELLOW_STAINED_CLAY);
SPECIAL_TERRAIN_MAPPING.put(Material.LIME_CLAY, Terrain.LIME_STAINED_CLAY);
SPECIAL_TERRAIN_MAPPING.put(Material.PINK_CLAY, Terrain.PINK_STAINED_CLAY);
SPECIAL_TERRAIN_MAPPING.put(Material.GREY_CLAY, Terrain.GREY_STAINED_CLAY);
SPECIAL_TERRAIN_MAPPING.put(Material.LIGHT_GREY_CLAY, Terrain.LIGHT_GREY_STAINED_CLAY);
SPECIAL_TERRAIN_MAPPING.put(Material.CYAN_CLAY, Terrain.CYAN_STAINED_CLAY);
SPECIAL_TERRAIN_MAPPING.put(Material.PURPLE_CLAY, Terrain.PURPLE_STAINED_CLAY);
SPECIAL_TERRAIN_MAPPING.put(Material.BLUE_CLAY, Terrain.BLUE_STAINED_CLAY);
SPECIAL_TERRAIN_MAPPING.put(Material.BROWN_CLAY, Terrain.BROWN_STAINED_CLAY);
SPECIAL_TERRAIN_MAPPING.put(Material.GREEN_CLAY, Terrain.GREEN_STAINED_CLAY);
SPECIAL_TERRAIN_MAPPING.put(Material.RED_CLAY, Terrain.RED_STAINED_CLAY);
SPECIAL_TERRAIN_MAPPING.put(Material.BLACK_CLAY, Terrain.BLACK_STAINED_CLAY);
SPECIAL_TERRAIN_MAPPING.put(Material.GRANITE, Terrain.GRANITE);
SPECIAL_TERRAIN_MAPPING.put(Material.DIORITE, Terrain.DIORITE);
SPECIAL_TERRAIN_MAPPING.put(Material.ANDESITE, Terrain.ANDESITE);
// Make sure the tile entity flag in the block database is consistent
// with the tile entity map:
Set<Integer> allTerrainBlockIds = new HashSet<>();
allTerrainBlockIds.addAll(TERRAIN_MAPPING.keySet());
for (int blockId: TERRAIN_MAPPING.keySet()) {
if (! Block.BLOCKS[blockId].terrain) {
throw new AssertionError("Block " + blockId + " not marked as terrain block!");
}
}
for (Material material: SPECIAL_TERRAIN_MAPPING.keySet()) {
allTerrainBlockIds.add(material.blockType);
if (! material.block.terrain) {
throw new AssertionError("Block " + material.blockType + " not marked as terrain block!");
}
}
for (Block block: Block.BLOCKS) {
if (block.terrain && (! allTerrainBlockIds.contains(block.id))) {
throw new AssertionError("Block " + block.id + " marked as terrain but not present in terrain type map!");
}
}
// Gather natural blocks:
for (Block block: Block.BLOCKS) {
if (block.natural) {
NATURAL_BLOCKS.set(block.id);
}
}
// Consider dungeons as natural for historical reasons:
NATURAL_BLOCKS.set(BLK_MONSTER_SPAWNER);
NATURAL_BLOCKS.set(BLK_CHEST);
NATURAL_BLOCKS.set(BLK_COBBLESTONE);
NATURAL_BLOCKS.set(BLK_MOSSY_COBBLESTONE);
}
public enum ReadOnlyOption {NONE, MAN_MADE, MAN_MADE_ABOVE_GROUND, ALL}
}