/* * To change this template, choose Tools | Templates * and open the template in the editor. */ package org.pepsoft.minecraft; import org.jnbt.*; import org.pepsoft.worldpainter.AccessDeniedException; import org.pepsoft.worldpainter.Generator; import java.io.*; import java.util.*; import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; import static org.pepsoft.minecraft.Constants.*; /** * * @author pepijn */ public final class Level extends AbstractNBTItem { public Level(int mapHeight, int version) { super(new CompoundTag(TAG_DATA, new HashMap<>())); if ((version != SUPPORTED_VERSION_1) && (version != SUPPORTED_VERSION_2)) { throw new IllegalArgumentException("Not a supported version: 0x" + Integer.toHexString(version)); } if ((mapHeight & (mapHeight - 1)) != 0) { throw new IllegalArgumentException("mapHeight " + mapHeight + " not a power of two"); } if (mapHeight != ((version == SUPPORTED_VERSION_1) ? DEFAULT_MAX_HEIGHT_1 : DEFAULT_MAX_HEIGHT_2)) { setInt(TAG_MAP_HEIGHT, mapHeight); } this.maxHeight = mapHeight; extraTags = null; setInt(TAG_VERSION, version); addDimension(0); } public Level(CompoundTag tag, int mapHeight) { super((CompoundTag) tag.getTag(TAG_DATA)); if ((mapHeight & (mapHeight - 1)) != 0) { throw new IllegalArgumentException("mapHeight " + mapHeight + " not a power of two"); } int version = getInt(TAG_VERSION); if ((version != SUPPORTED_VERSION_1) && (version != SUPPORTED_VERSION_2)) { throw new IllegalArgumentException("Not a supported version: 0x" + Integer.toHexString(version)); } if (mapHeight != ((version == SUPPORTED_VERSION_1) ? DEFAULT_MAX_HEIGHT_1 : DEFAULT_MAX_HEIGHT_2)) { setInt(TAG_MAP_HEIGHT, mapHeight); } this.maxHeight = mapHeight; if (tag.getValue().size() == 1) { // No extra tags extraTags = null; } else { // The root tag contains extra tags, most likely from mods. Preserve them (but filter out the data tag) extraTags = new HashSet<>(); tag.getValue().values().stream() .filter(extraTag -> ! extraTag.getName().equals(TAG_DATA)) .forEach(extraTags::add); } addDimension(0); } public void save(File worldDir) throws IOException { if (! worldDir.exists()) { if (! worldDir.mkdirs()) { throw new AccessDeniedException("Could not create directory " + worldDir); } } // Write session.lock file File sessionLockFile = new File(worldDir, "session.lock"); try (DataOutputStream sessionOut = new DataOutputStream(new FileOutputStream(sessionLockFile))) { sessionOut.writeLong(System.currentTimeMillis()); } // Write level.dat file File levelDatFile = new File(worldDir, "level.dat"); // Make it show at the top of the single player map list: setLong(TAG_LAST_PLAYED, System.currentTimeMillis()); try (NBTOutputStream out = new NBTOutputStream(new GZIPOutputStream(new FileOutputStream(levelDatFile)))) { out.writeTag(toNBT()); } // If height is non-standard, write DynamicHeight mod and Height Mod // files int mapHeight = getMapHeight(); if (mapHeight != 0) { int exp = (int) (Math.log(mapHeight) / Math.log(2)); PrintWriter writer = new PrintWriter(new File(worldDir, "maxheight.txt"), "US-ASCII"); try { writer.println("#DynamicHeight Save Format 2"); writer.println("#" + new Date()); writer.println("height=" + exp); } finally { writer.close(); } writer = new PrintWriter(new File(worldDir, "Height.txt"), "US-ASCII"); try { writer.println("#HeightMod 1.5"); writer.println("#" + new Date()); writer.println("height=" + exp); writer.println("version=1"); writer.println("midheight=" + mapHeight + ".0"); writer.println("waterlevel=" + (mapHeight / 2 - 2) + ".0"); writer.println("genheight=" + mapHeight + ".0"); } finally { writer.close(); } } } public Dimension getDimension(int dim) { return dimensions.get(dim); } public void addDimension(int dim) { if (dimensions.containsKey(dim)) { throw new IllegalStateException("Dimension " + dim + " already exists"); } else { dimensions.put(dim, new Dimension(dim, maxHeight)); } } public Dimension removeDimension(int dim) { return dimensions.remove(dim); } public String getName() { return getString(TAG_LEVEL_NAME); } public long getSeed() { return getLong(TAG_RANDOM_SEED); } public int getSpawnX() { return getInt(TAG_SPAWN_X); } public int getSpawnY() { return getInt(TAG_SPAWN_Y); } public int getSpawnZ() { return getInt(TAG_SPAWN_Z); } public long getTime() { return getLong(TAG_TIME); } public int getVersion() { return getInt(TAG_VERSION); } public boolean isMapFeatures() { return getBoolean(TAG_MAP_FEATURES); } public int getMapHeight() { return getInt(TAG_MAP_HEIGHT); } public int getGameType() { return getInt(TAG_GAME_TYPE); } public boolean isHardcore() { return getBoolean(TAG_HARDCORE); } public String getGeneratorName() { return getString(TAG_GENERATOR_NAME); } public int getGeneratorVersion() { return getInt(TAG_GENERATOR_VERSION); } public Generator getGenerator() { if ("FLAT".equals(getGeneratorName()) || "flat".equals(getGeneratorName())) { return Generator.FLAT; } else if ("largeBiomes".equals(getGeneratorName())) { return Generator.LARGE_BIOMES; } else { return Generator.DEFAULT; } } public String getGeneratorOptions() { return getString(TAG_GENERATOR_OPTIONS); } public boolean isAllowCommands() { return getBoolean(TAG_ALLOW_COMMANDS); } public int getMaxHeight() { return maxHeight; } public int getDifficulty() { return getByte(TAG_DIFFICULTY); } public boolean isDifficultyLocked() { return getBoolean(TAG_DIFFICULTY_LOCKED); } public double getBorderCenterX() { return getDouble(TAG_BORDER_CENTER_X); } public double getBorderCenterZ() { return getDouble(TAG_BORDER_CENTER_Z); } public double getBorderSize() { return getDouble(TAG_BORDER_SIZE); } public double getBorderSafeZone() { return getDouble(TAG_BORDER_SAFE_ZONE); } public double getBorderWarningBlocks() { return getDouble(TAG_BORDER_WARNING_BLOCKS); } public double getBorderWarningTime() { return getDouble(TAG_BORDER_WARNING_TIME); } public double getBorderSizeLerpTarget() { return getDouble(TAG_BORDER_SIZE_LERP_TARGET); } public long getBorderSizeLerpTime() { return getLong(TAG_BORDER_SIZE_LERP_TIME); } public double getBorderDamagePerBlock() { return getDouble(TAG_BORDER_DAMAGE_PER_BLOCK); } public void setName(String name) { setString(TAG_LEVEL_NAME, name); } public void setSeed(long seed) { setLong(TAG_RANDOM_SEED, seed); } public void setSpawnX(int spawnX) { setInt(TAG_SPAWN_X, spawnX); } public void setSpawnY(int spawnY) { setInt(TAG_SPAWN_Y, spawnY); } public void setSpawnZ(int spawnZ) { setInt(TAG_SPAWN_Z, spawnZ); } public void setTime(long time) { setLong(TAG_TIME, time); } public void setMapFeatures(boolean mapFeatures) { setBoolean(TAG_MAP_FEATURES, mapFeatures); } public void setGameType(int gameType) { setInt(TAG_GAME_TYPE, gameType); } public void setHardcore(boolean hardcore) { setBoolean(TAG_HARDCORE, hardcore); } public void setGenerator(Generator generator) { switch (generator) { case DEFAULT: if (getVersion() == SUPPORTED_VERSION_1) { setString(TAG_GENERATOR_NAME, "DEFAULT"); } else { setString(TAG_GENERATOR_NAME, "default"); setInt(TAG_GENERATOR_VERSION, 1); } break; case FLAT: if (getVersion() == SUPPORTED_VERSION_1) { setString(TAG_GENERATOR_NAME, "FLAT"); } else { setString(TAG_GENERATOR_NAME, "flat"); } break; case LARGE_BIOMES: if (getVersion() == SUPPORTED_VERSION_1) { throw new IllegalArgumentException("Large biomes not supported for Minecraft 1.1 maps"); } else { setString(TAG_GENERATOR_NAME, "largeBiomes"); setInt(TAG_GENERATOR_VERSION, 0); } break; default: throw new InternalError(); } } public void setGeneratorOptions(String generatorOptions) { setString(TAG_GENERATOR_OPTIONS, generatorOptions); } public void setAllowCommands(boolean allowCommands) { setBoolean(TAG_ALLOW_COMMANDS, allowCommands); } public void setDifficulty(int difficulty) { setByte(TAG_DIFFICULTY, (byte) difficulty); } public void setDifficultyLocked(boolean difficultyLocked) { setBoolean(TAG_DIFFICULTY_LOCKED, difficultyLocked); } public void setBorderCenterX(double borderCenterX) { setDouble(TAG_BORDER_CENTER_X, borderCenterX); } public void setBorderCenterZ(double borderCenterZ) { setDouble(TAG_BORDER_CENTER_Z, borderCenterZ); } public void setBorderSize(double borderSize) { setDouble(TAG_BORDER_SIZE, borderSize); } public void setBorderSafeZone(double borderSafeZone) { setDouble(TAG_BORDER_SAFE_ZONE, borderSafeZone); } public void setBorderWarningBlocks(double borderWarningBlocks) { setDouble(TAG_BORDER_WARNING_BLOCKS, borderWarningBlocks); } public void setBorderWarningTime(double borderWarningTime) { setDouble(TAG_BORDER_WARNING_TIME, borderWarningTime); } public void setBorderSizeLerpTarget(double borderSizeLerpTarget) { setDouble(TAG_BORDER_SIZE_LERP_TARGET, borderSizeLerpTarget); } public void setBorderSizeLerpTime(long borderSizeLerpTime) { setLong(TAG_BORDER_SIZE_LERP_TIME, borderSizeLerpTime); } public void setBorderDamagePerBlock(double borderDamagePerBlock) { setDouble(TAG_BORDER_DAMAGE_PER_BLOCK, borderDamagePerBlock); } @Override public Tag toNBT() { Map<String, Tag> values = new HashMap<>(); values.put(TAG_DATA, super.toNBT()); if (extraTags != null) { for (Tag extraTag: extraTags) { values.put(extraTag.getName(), extraTag); } } return new CompoundTag("", values); } public static Level load(File levelDatFile) throws IOException { Tag tag; try (NBTInputStream in = new NBTInputStream(new GZIPInputStream(new FileInputStream(levelDatFile)))) { tag = in.readTag(); } int version = ((IntTag) ((CompoundTag) ((CompoundTag) tag).getTag(TAG_DATA)).getTag(TAG_VERSION)).getValue(); int maxHeight = (version == SUPPORTED_VERSION_1) ? DEFAULT_MAX_HEIGHT_1 : DEFAULT_MAX_HEIGHT_2; if (version == SUPPORTED_VERSION_1) { if (((CompoundTag) ((CompoundTag) tag).getTag(TAG_DATA)).getTag(TAG_MAP_HEIGHT) != null) { maxHeight = ((IntTag) ((CompoundTag) ((CompoundTag) tag).getTag(TAG_DATA)).getTag(TAG_MAP_HEIGHT)).getValue(); } else { File maxheightFile = new File(levelDatFile.getParentFile(), "maxheight.txt"); if (! maxheightFile.isFile()) { maxheightFile = new File(levelDatFile.getParentFile(), "Height.txt"); } if (maxheightFile.isFile()) { try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(maxheightFile), "US-ASCII"))) { String line; while ((line = reader.readLine()) != null) { if (line.startsWith("height=")) { int exp = Integer.parseInt(line.substring(7)); maxHeight = 1 << exp; break; } } } } } } return new Level((CompoundTag) tag, maxHeight); } private final int maxHeight; private final Map<Integer, Dimension> dimensions = new HashMap<>(); private final Set<Tag> extraTags; private static final long serialVersionUID = 1L; }