package net.glowstone.io.nbt;
import net.glowstone.GlowServer;
import net.glowstone.GlowWorld;
import net.glowstone.GlowWorldBorder;
import net.glowstone.io.WorldMetadataService;
import net.glowstone.util.nbt.CompoundTag;
import net.glowstone.util.nbt.NBTInputStream;
import net.glowstone.util.nbt.NBTOutputStream;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.WorldType;
import java.io.*;
import java.util.Calendar;
import java.util.UUID;
import java.util.logging.Level;
public class NbtWorldMetadataService implements WorldMetadataService {
private final GlowWorld world;
private final File dir;
private final GlowServer server;
private CompoundTag unknownTags;
public NbtWorldMetadataService(GlowWorld world, File dir) {
this.world = world;
this.dir = dir;
server = (GlowServer) Bukkit.getServer();
if (!dir.isDirectory() && !dir.mkdirs()) {
server.getLogger().warning("Failed to create directory: " + dir);
}
}
@Override
public WorldFinalValues readWorldData() {
// determine UUID of world
UUID uid = null;
File uuidFile = new File(dir, "uid.dat");
if (uuidFile.exists()) {
try (DataInputStream in = new DataInputStream(new FileInputStream(uuidFile))) {
uid = new UUID(in.readLong(), in.readLong());
} catch (IOException e) {
handleWorldException("uid.dat", e);
}
}
if (uid == null) {
uid = UUID.randomUUID();
}
// read in world information
CompoundTag level = new CompoundTag();
File levelFile = new File(dir, "level.dat");
if (levelFile.exists()) {
try (NBTInputStream in = new NBTInputStream(new FileInputStream(levelFile))) {
level = in.readCompound();
if (level.isCompound("Data")) {
level = level.getCompound("Data");
} else {
server.getLogger().warning("Loading world \"" + world.getName() + "\": reading from root, not Data");
}
} catch (IOException e) {
handleWorldException("level.dat", e);
}
}
// seed
long seed = 0L;
if (level.isLong("RandomSeed")) {
seed = level.getLong("RandomSeed");
level.remove("RandomSeed");
}
// time of day and weather status
if (level.isByte("thundering")) {
world.setThundering(level.getBool("thundering"));
level.remove("thundering");
}
if (level.isByte("raining")) {
world.setStorm(level.getBool("raining"));
level.remove("raining");
}
if (level.isInt("thunderTime")) {
world.setThunderDuration(level.getInt("thunderTime"));
level.remove("thunderTime");
}
if (level.isInt("rainTime")) {
world.setWeatherDuration(level.getInt("rainTime"));
level.remove("rainTime");
}
if (level.isLong("Time")) {
world.setFullTime(level.getLong("Time"));
level.remove("Time");
}
if (level.isLong("DayTime")) {
world.setTime(level.getLong("DayTime"));
level.remove("DayTime");
}
if (level.isString("generatorName")) {
world.setWorldType(WorldType.getByName(level.getString("generatorName")));
level.remove("generatorName");
}
// spawn position
if (level.isInt("SpawnX") && level.isInt("SpawnY") && level.isInt("SpawnZ")) {
world.setSpawnLocation(level.getInt("SpawnX"), level.getInt("SpawnY"), level.getInt("SpawnZ"));
level.remove("SpawnX");
level.remove("SpawnY");
level.remove("SpawnZ");
}
// game rules
if (level.isCompound("GameRules")) {
CompoundTag gameRules = level.getCompound("GameRules");
gameRules.getValue().keySet().stream().filter(gameRules::isString).forEach(key -> world.setGameRuleValue(key, gameRules.getString(key)));
level.remove("GameRules");
}
// world border
Location borderCenter = new Location(world, 0, 0, 0);
if (level.isDouble("BorderCenterX")) {
borderCenter.setX(level.getDouble("BorderCenterX"));
level.remove("BorderCenterX");
}
if (level.isDouble("BorderCenterZ")) {
borderCenter.setZ(level.getDouble("BorderCenterZ"));
level.remove("BorderCenterZ");
}
world.getWorldBorder().setCenter(borderCenter);
if (level.isDouble("BorderSize")) {
world.getWorldBorder().setSize(level.getDouble("BorderSize"));
level.remove("BorderSize");
}
if (level.isDouble("BorderSizeLerpTarget") && level.isLong("BorderSizeLerpTime")) {
world.getWorldBorder().setSize(level.getDouble("BorderSizeLerpTarget"), level.getLong("BorderSizeLerpTime"));
level.remove("BorderSizeLerpTarget");
level.remove("BorderSizeLerpTime");
}
if (level.isDouble("BorderSafeZone")) {
world.getWorldBorder().setDamageBuffer(level.getDouble("BorderSafeZone"));
level.remove("BorderSafeZone");
}
if (level.isDouble("BorderWarningTime")) {
world.getWorldBorder().setWarningTime((int) level.getDouble("BorderWarningTime"));
level.remove("BorderWarningTime");
}
if (level.isDouble("BorderWarningBlocks")) {
world.getWorldBorder().setWarningDistance((int) level.getDouble("BorderWarningBlocks"));
level.remove("BorderWarningBlocks");
}
if (level.isDouble("BorderDamagePerBlock")) {
world.getWorldBorder().setDamageAmount(level.getDouble("BorderDamagePerBlock"));
level.remove("BorderDamagePerBlock");
}
// strip single-player Player tag if it exists
if (level.isCompound("Player")) {
server.getLogger().warning("World \"" + world.getName() + "\": removing single-player Player tag");
level.remove("Player");
}
// save unknown tags for later
unknownTags = level;
return new WorldFinalValues(seed, uid);
}
private void handleWorldException(String file, IOException e) {
server.unloadWorld(world, false);
server.getLogger().log(Level.SEVERE, "Unable to access " + file + " for world " + world.getName(), e);
}
@Override
public void writeWorldData() throws IOException {
File uuidFile = new File(dir, "uid.dat");
try (DataOutputStream out = new DataOutputStream(new FileOutputStream(uuidFile))) {
UUID uuid = world.getUID();
out.writeLong(uuid.getMostSignificantBits());
out.writeLong(uuid.getLeastSignificantBits());
}
// start with unknown tags from reading
CompoundTag out = new CompoundTag();
if (unknownTags != null) {
out.getValue().putAll(unknownTags.getValue());
}
// Seed and core information
out.putString("LevelName", world.getName());
out.putInt("version", 19133);
out.putLong("LastPlayed", Calendar.getInstance().getTimeInMillis());
out.putLong("RandomSeed", world.getSeed());
// Normal level data
out.putLong("Time", world.getFullTime());
out.putLong("DayTime", world.getTime());
out.putBool("thundering", world.isThundering());
out.putBool("raining", world.hasStorm());
out.putInt("thunderTime", world.getThunderDuration());
out.putInt("rainTime", world.getWeatherDuration());
out.putString("generatorName", world.getWorldType().getName().toLowerCase());
// Spawn location
Location loc = world.getSpawnLocation();
out.putInt("SpawnX", loc.getBlockX());
out.putInt("SpawnY", loc.getBlockY());
out.putInt("SpawnZ", loc.getBlockZ());
// World border
out.putDouble("BorderCenterX", world.getWorldBorder().getCenter().getX());
out.putDouble("BorderCenterZ", world.getWorldBorder().getCenter().getZ());
out.putDouble("BorderSize", world.getWorldBorder().getSize());
out.putDouble("BorderSizeLerpTarget", ((GlowWorldBorder) world.getWorldBorder()).futureSize);
out.putLong("BorderSizeLerpTime", ((GlowWorldBorder) world.getWorldBorder()).time);
out.putDouble("BorderSafeZone", world.getWorldBorder().getDamageBuffer());
out.putDouble("BorderWarningTime", world.getWorldBorder().getWarningTime());
out.putDouble("BorderWarningBlocks", world.getWorldBorder().getWarningDistance());
out.putDouble("BorderDamagePerBlock", world.getWorldBorder().getDamageAmount());
// Game rules
CompoundTag gameRules = new CompoundTag();
String[] gameRuleKeys = world.getGameRules();
for (String key : gameRuleKeys) {
gameRules.putString(key, world.getGameRuleValue(key));
}
out.putCompound("GameRules", gameRules);
// Not sure how to calculate this, so ignoring for now
out.putLong("SizeOnDisk", 0);
CompoundTag root = new CompoundTag();
root.putCompound("Data", out);
try (NBTOutputStream nbtOut = new NBTOutputStream(new FileOutputStream(new File(dir, "level.dat")))) {
nbtOut.writeTag(root);
} catch (IOException e) {
handleWorldException("level.dat", e);
}
}
}