/*
* This file is part of LanternServer, licensed under the MIT License (MIT).
*
* Copyright (c) LanternPowered <https://www.lanternpowered.org>
* Copyright (c) SpongePowered <https://www.spongepowered.org>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the Software), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.lanternpowered.server.world;
import com.flowpowered.math.vector.Vector3i;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import org.lanternpowered.server.config.world.WorldConfig;
import org.lanternpowered.server.data.io.IOHelper;
import org.lanternpowered.server.data.persistence.nbt.NbtStreamUtils;
import org.lanternpowered.server.data.translator.JsonTranslator;
import org.lanternpowered.server.data.util.DataQueries;
import org.lanternpowered.server.data.world.MoonPhase;
import org.lanternpowered.server.entity.living.player.gamemode.LanternGameMode;
import org.lanternpowered.server.game.Lantern;
import org.lanternpowered.server.game.registry.type.entity.player.GameModeRegistryModule;
import org.lanternpowered.server.game.registry.type.world.DifficultyRegistryModule;
import org.lanternpowered.server.world.difficulty.LanternDifficulty;
import org.lanternpowered.server.world.dimension.LanternDimensionType;
import org.lanternpowered.server.world.gen.flat.FlatGeneratorType;
import org.lanternpowered.server.world.rules.RuleDataTypes;
import org.lanternpowered.server.world.rules.RuleType;
import org.lanternpowered.server.world.weather.LanternWeather;
import org.spongepowered.api.CatalogType;
import org.spongepowered.api.Sponge;
import org.spongepowered.api.data.DataContainer;
import org.spongepowered.api.data.DataQuery;
import org.spongepowered.api.data.DataView;
import org.spongepowered.api.data.MemoryDataContainer;
import org.spongepowered.api.entity.living.player.gamemode.GameModes;
import org.spongepowered.api.world.DimensionType;
import org.spongepowered.api.world.DimensionTypes;
import org.spongepowered.api.world.GeneratorType;
import org.spongepowered.api.world.GeneratorTypes;
import org.spongepowered.api.world.PortalAgentType;
import org.spongepowered.api.world.PortalAgentTypes;
import org.spongepowered.api.world.SerializationBehaviors;
import org.spongepowered.api.world.difficulty.Difficulties;
import org.spongepowered.api.world.weather.Weather;
import org.spongepowered.api.world.weather.Weathers;
import java.io.DataInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.List;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
final class LanternWorldPropertiesIO {
private final static Gson GSON = new Gson();
private final static String LEVEL_DATA = "level.dat";
private final static String SPONGE_LEVEL_DATA = "level_sponge.dat";
private final static String BUKKIT_UUID_DATA = "uid.dat";
// The current version of the worlds
private final static int CURRENT_VERSION = 19133;
// Vanilla properties
private final static DataQuery DATA = DataQuery.of("Data");
private final static DataQuery SEED = DataQuery.of("RandomSeed");
private final static DataQuery INITIALIZED = DataQuery.of("initialized");
private final static DataQuery CLEAR_WEATHER_TIME = DataQuery.of("clearWeatherTime");
private final static DataQuery THUNDERING = DataQuery.of("thundering");
private final static DataQuery THUNDER_TIME = DataQuery.of("thunderTime");
private final static DataQuery RAINING = DataQuery.of("raining");
private final static DataQuery RAIN_TIME = DataQuery.of("rainTime");
private final static DataQuery AGE = DataQuery.of("Time");
private final static DataQuery TIME = DataQuery.of("DayTime");
private final static DataQuery SPAWN_X = DataQuery.of("SpawnX");
private final static DataQuery SPAWN_Y = DataQuery.of("SpawnY");
private final static DataQuery SPAWN_Z = DataQuery.of("SpawnZ");
private final static DataQuery GAME_RULES = DataQuery.of("GameRules");
private final static DataQuery HARDCORE = DataQuery.of("hardcore");
private final static DataQuery VERSION = DataQuery.of("version");
private final static DataQuery NAME = DataQuery.of("LevelName");
private final static DataQuery LAST_PLAYED = DataQuery.of("LastPlayed");
private final static DataQuery DIFFICULTY = DataQuery.of("Difficulty");
private final static DataQuery DIFFICULTY_LOCKED = DataQuery.of("DifficultyLocked");
private final static DataQuery SIZE_ON_DISK = DataQuery.of("SizeOnDisk");
private final static DataQuery MAP_FEATURES = DataQuery.of("MapFeatures");
private final static DataQuery GAME_MODE = DataQuery.of("GameType");
private final static DataQuery BORDER_CENTER_X = DataQuery.of("BorderCenterX");
private final static DataQuery BORDER_CENTER_Z = DataQuery.of("BorderCenterZ");
private final static DataQuery BORDER_SIZE_START = DataQuery.of("BorderSize");
private final static DataQuery BORDER_SIZE_END = DataQuery.of("BorderSizeLerpTarget");
private final static DataQuery BORDER_SIZE_LERP_TIME = DataQuery.of("BorderSizeLerpTime");
private final static DataQuery BORDER_DAMAGE = DataQuery.of("BorderDamagePerBlock");
private final static DataQuery BORDER_DAMAGE_THRESHOLD = DataQuery.of("BorderSafeZone");
private final static DataQuery BORDER_WARNING_BLOCKS = DataQuery.of("BorderWarningBlocks");
private final static DataQuery BORDER_WARNING_TIME = DataQuery.of("BorderWarningBlocks");
private final static DataQuery GENERATOR_NAME = DataQuery.of("generatorName");
private final static DataQuery GENERATOR_VERSION = DataQuery.of("generatorVersion");
private final static DataQuery GENERATOR_OPTIONS = DataQuery.of("generatorOptions");
// Forge properties
private final static DataQuery FORGE = DataQuery.of("Forge");
private final static DataQuery DIMENSION_DATA = DataQuery.of("DimensionData");
private final static DataQuery DIMENSION_ARRAY = DataQuery.of("DimensionArray");
// Sponge properties
private final static DataQuery UUID_MOST = DataQuery.of("UUIDMost");
private final static DataQuery UUID_LEAST = DataQuery.of("UUIDLeast");
private final static DataQuery OLD_UUID_MOST = DataQuery.of("uuid_most");
private final static DataQuery OLD_UUID_LEAST = DataQuery.of("uuid_least");
private final static DataQuery PORTAL_AGENT_TYPE = DataQuery.of("portalAgentType");
private final static DataQuery DIMENSION_TYPE = DataQuery.of("dimensionType");
private final static DataQuery DIMENSION_INDEX = DataQuery.of("dimensionId");
private final static DataQuery GENERATOR_MODIFIERS = DataQuery.of("generatorModifiers");
private final static DataQuery TRACKER_UUID_TABLE = DataQuery.of("PlayerIdTable");
private final static DataQuery ENABLED = DataQuery.of("enabled");
private final static DataQuery KEEP_SPAWN_LOADED = DataQuery.of("keepSpawnLoaded");
private final static DataQuery LOAD_ON_STARTUP = DataQuery.of("loadOnStartup");
private final static DataQuery GENERATE_BONUS_CHEST = DataQuery.of("GenerateBonusChest");
private final static DataQuery SERIALIZATION_BEHAVIOR = DataQuery.of("serializationBehavior");
// Extra generator options for the flat world generator type
private final static DataQuery GENERATOR_OPTIONS_EXTRA = DataQuery.of("generatorOptionsExtra");
private final static DataQuery LANTERN = DataQuery.of("LanternData");
private final static DataQuery WEATHER = DataQuery.of("weather");
private final static DataQuery WEATHER_TYPE = DataQuery.of("type");
private final static DataQuery WEATHER_RUNNING_DURATION = DataQuery.of("running");
private final static DataQuery WEATHER_REMAINING_DURATION = DataQuery.of("remaining");
private final static DataQuery MOON_PHASE = DataQuery.of("moonPhase");
// Provider class names in vanilla
private final static String OVERWORLD = "net.minecraft.world.WorldProviderSurface";
private final static String NETHER = "net.minecraft.world.WorldProviderHell";
private final static String END = "net.minecraft.world.WorldProviderEnd";
private LanternWorldPropertiesIO() {
}
static LevelData read(Path directory, @Nullable String worldName, @Nullable UUID uniqueId) throws IOException {
final DataView rootDataView = IOHelper.read(directory.resolve(LEVEL_DATA), file -> NbtStreamUtils.read(Files.newInputStream(file), true))
.orElseThrow(() -> new FileNotFoundException("Unable to find " + LEVEL_DATA + "!"));
final DataView dataView = rootDataView.getView(DATA).get();
if (worldName == null) {
worldName = dataView.getString(NAME).get();
}
final DataView spongeRootDataView = IOHelper.read(directory.resolve(SPONGE_LEVEL_DATA),
file -> NbtStreamUtils.read(Files.newInputStream(file), true)).orElse(null);
final DataView spongeContainer = spongeRootDataView != null ? spongeRootDataView.getView(DataQueries.SPONGE_DATA).orElse(null) : null;
if (uniqueId == null) {
// Try for the sponge (lantern) storage format
if (spongeContainer != null) {
final Long most = spongeContainer.getLong(UUID_MOST).orElseGet(() -> spongeContainer.getLong(OLD_UUID_MOST).orElse(null));
final Long least = spongeContainer.getLong(UUID_LEAST).orElseGet(() -> spongeContainer.getLong(OLD_UUID_LEAST).orElse(null));
if (most != null && least != null) {
uniqueId = new UUID(most, least);
}
}
// The uuid storage bukkit used, try this one first
final Path uuidFile;
if (uniqueId == null && Files.exists((uuidFile = directory.resolve(BUKKIT_UUID_DATA)))) {
try (DataInputStream in = new DataInputStream(Files.newInputStream(uuidFile))) {
uniqueId = new UUID(in.readLong(), in.readLong());
} catch (IOException e) {
Lantern.getLogger().error("Unable to access {}, ignoring...", BUKKIT_UUID_DATA, e);
}
Files.delete(uuidFile);
}
if (uniqueId == null) {
uniqueId = UUID.randomUUID();
}
}
BitSet dimensionMap = null;
if (rootDataView.contains(FORGE)) {
final DataView forgeView = rootDataView.getView(FORGE).get();
if (forgeView.contains(DIMENSION_DATA)) {
dimensionMap = new BitSet(LanternWorldManager.DIMENSION_MAP_SIZE);
final int[] intArray = (int[]) forgeView.getView(DIMENSION_DATA).get().get(DIMENSION_ARRAY).get();
for (int i = 0; i < intArray.length; i++) {
for (int j = 0; j < Integer.SIZE; j++) {
dimensionMap.set(i * Integer.SIZE + j, (intArray[i] & (1 << j)) != 0);
}
}
}
}
final Integer dimensionId = spongeContainer != null ? spongeContainer.getInt(DIMENSION_INDEX).orElse(null) : null;
return new LevelData(worldName, uniqueId, rootDataView, spongeRootDataView, dimensionId, dimensionMap);
}
static LanternWorldProperties convert(LevelData levelData, WorldConfig worldConfig, boolean copyLevelSettingsToConfig) {
final LanternWorldProperties properties = new LanternWorldProperties(levelData.uniqueId, levelData.worldName, worldConfig);
final DataView dataView = levelData.worldData.getView(DATA).get();
final DataView spongeRootDataView = levelData.spongeWorldData;
final DataView spongeDataView;
if (spongeRootDataView != null) {
spongeDataView = spongeRootDataView.getView(DataQueries.SPONGE_DATA).orElse(null);
spongeDataView.remove(DataQueries.SPONGE_DATA);
} else {
spongeDataView = null;
}
final DataView lanternDataView = spongeDataView == null ? null : spongeDataView.getView(LANTERN).orElse(null);
properties.setLastPlayedTime(dataView.getLong(LAST_PLAYED).get());
properties.mapFeatures = dataView.getInt(MAP_FEATURES).get() > 0;
properties.setInitialized(dataView.getInt(INITIALIZED).get() > 0);
dataView.getInt(DIFFICULTY_LOCKED).ifPresent(v -> properties.setDifficultyLocked(v > 0));
dataView.getDouble(BORDER_CENTER_X).ifPresent(v -> properties.borderCenterX = v);
dataView.getDouble(BORDER_CENTER_Z).ifPresent(v -> properties.borderCenterZ = v);
dataView.getDouble(BORDER_SIZE_START).ifPresent(v -> properties.borderDiameterStart = v);
dataView.getDouble(BORDER_SIZE_END).ifPresent(v -> properties.borderDiameterEnd = v);
dataView.getLong(BORDER_SIZE_LERP_TIME).ifPresent(v -> properties.borderLerpTime = v);
dataView.getDouble(BORDER_DAMAGE).ifPresent(v -> properties.borderDamage = v);
dataView.getDouble(BORDER_DAMAGE_THRESHOLD).ifPresent(v -> properties.borderDamageThreshold = v);
dataView.getInt(BORDER_WARNING_BLOCKS).ifPresent(v -> properties.borderWarningDistance = v);
dataView.getInt(BORDER_WARNING_TIME).ifPresent(v -> properties.borderWarningTime = v);
if (spongeRootDataView != null) {
properties.setAdditionalProperties(spongeRootDataView.copy().remove(DataQueries.SPONGE_DATA));
}
// Get the sponge properties
if (spongeDataView != null) {
// This can be null, this is provided in the lantern-server
final String dimensionTypeId = spongeDataView.getString(DIMENSION_TYPE).get();
if (dimensionTypeId.equalsIgnoreCase(OVERWORLD)) {
properties.setDimensionType(DimensionTypes.OVERWORLD);
} else if (dimensionTypeId.equalsIgnoreCase(NETHER)) {
properties.setDimensionType(DimensionTypes.NETHER);
} else if (dimensionTypeId.equalsIgnoreCase(END)) {
properties.setDimensionType(DimensionTypes.THE_END);
} else {
final DimensionType dimensionType = Sponge.getRegistry().getType(DimensionType.class, dimensionTypeId).orElse(null);
if (dimensionType == null) {
Lantern.getLogger().warn("Could not find a dimension type with id {} for the world {}, falling back to overworld...",
dimensionTypeId, levelData.worldName);
}
properties.setDimensionType(dimensionType == null ? DimensionTypes.OVERWORLD : dimensionType);
}
PortalAgentType portalAgentType = null;
if (spongeDataView.contains(PORTAL_AGENT_TYPE)) {
final String portalAgentTypeId = spongeDataView.getString(PORTAL_AGENT_TYPE).get();
portalAgentType = Sponge.getRegistry().getType(PortalAgentType.class, portalAgentTypeId).orElse(null);
if (portalAgentType == null) {
Lantern.getLogger().warn("Could not find a portal agent type with id {} for the world {}, falling back to default...",
portalAgentTypeId, levelData.worldName);
}
}
properties.setPortalAgentType(portalAgentType == null ? PortalAgentTypes.DEFAULT : portalAgentType);
spongeDataView.getInt(GENERATE_BONUS_CHEST).ifPresent(v -> properties.setGenerateBonusChest(v > 0));
spongeDataView.getInt(SERIALIZATION_BEHAVIOR).ifPresent(v -> properties.setSerializationBehavior(
v == 0 ? SerializationBehaviors.MANUAL : v == 1 ? SerializationBehaviors.AUTOMATIC : SerializationBehaviors.NONE));
// Tracker
final Optional<List<DataView>> optTrackerUniqueIdViews = spongeDataView.getViewList(TRACKER_UUID_TABLE);
if (optTrackerUniqueIdViews.isPresent()) {
final List<DataView> trackerUniqueIdViews = optTrackerUniqueIdViews.get();
final Object2IntMap<UUID> trackerUniqueIds = properties.getTrackerIdAllocator().getUniqueIds();
final List<UUID> uniqueIdsByIndex = properties.getTrackerIdAllocator().getUniqueIdsByIndex();
for (DataView view : trackerUniqueIdViews) {
UUID uniqueId = null;
if (!view.isEmpty()) {
final long most = view.getLong(UUID_MOST).get();
final long least = view.getLong(UUID_LEAST).get();
uniqueId = new UUID(most, least);
trackerUniqueIds.put(uniqueId, uniqueIdsByIndex.size());
}
uniqueIdsByIndex.add(uniqueId);
}
}
}
// Weather
final WeatherData weatherData = properties.getWeatherData();
if (lanternDataView != null) {
final DataView weatherView = lanternDataView.getView(WEATHER).get();
final String weatherTypeId = weatherView.getString(WEATHER_TYPE).get();
final Optional<Weather> weatherType = Sponge.getRegistry().getType(Weather.class, weatherTypeId);
if (weatherType.isPresent()) {
weatherData.setWeather((LanternWeather) weatherType.get());
} else {
Lantern.getLogger().info("Unknown weather type: {}, the server will default to {}",
weatherTypeId, weatherData.getWeather().getId());
}
weatherData.setRunningDuration(weatherView.getLong(WEATHER_RUNNING_DURATION).get());
weatherData.setRemainingDuration(weatherView.getLong(WEATHER_REMAINING_DURATION).get());
} else {
final boolean raining = dataView.getInt(RAINING).get() > 0;
final long rainTime = dataView.getLong(RAIN_TIME).get();
final boolean thundering = dataView.getInt(THUNDERING).get() > 0;
final long thunderTime = dataView.getLong(THUNDER_TIME).get();
final long clearWeatherTime = dataView.getLong(CLEAR_WEATHER_TIME).get();
if (thundering) {
weatherData.setWeather((LanternWeather) Weathers.THUNDER_STORM);
weatherData.setRemainingDuration(thunderTime);
} else if (raining) {
weatherData.setWeather((LanternWeather) Weathers.RAIN);
weatherData.setRemainingDuration(rainTime);
} else {
weatherData.setRemainingDuration(clearWeatherTime);
}
}
// Time
final TimeData timeData = properties.getTimeData();
final long age = dataView.getLong(AGE).get();
timeData.setAge(age);
final long time = dataView.getLong(TIME).orElse(age);
timeData.setDayTime(time);
if (lanternDataView != null && lanternDataView.contains(MOON_PHASE)) {
timeData.setMoonPhase(MoonPhase.valueOf(lanternDataView.getString(MOON_PHASE).get().toUpperCase()));
} else {
timeData.setMoonPhase(MoonPhase.values()[(int) (time / TimeUniverse.TICKS_IN_A_DAY) % 8]);
}
// Get the spawn position
final Optional<Integer> spawnX = dataView.getInt(SPAWN_X);
final Optional<Integer> spawnY = dataView.getInt(SPAWN_Y);
final Optional<Integer> spawnZ = dataView.getInt(SPAWN_Z);
if (spawnX.isPresent() && spawnY.isPresent() && spawnZ.isPresent()) {
properties.setSpawnPosition(new Vector3i(spawnX.get(), spawnY.get(), spawnZ.get()));
}
// Get the game rules
final DataView rulesView = dataView.getView(GAME_RULES).orElse(null);
if (rulesView != null) {
for (Entry<DataQuery, Object> en : rulesView.getValues(false).entrySet()) {
try {
properties.getRules()
.getOrCreateRule(RuleType.getOrCreate(en.getKey().toString(), RuleDataTypes.STRING, ""))
.setRawValue((String) en.getValue());
} catch (IllegalArgumentException e) {
Lantern.getLogger().warn("An error occurred while loading a game rule ({}) this one will be skipped",
en.getKey().toString(), e);
}
}
}
if (copyLevelSettingsToConfig) {
worldConfig.getGeneration().setSeed(dataView.getLong(SEED).get());
worldConfig.setGameMode(GameModeRegistryModule.get().getByInternalId(dataView.getInt(GAME_MODE).get())
.orElse(GameModes.SURVIVAL));
worldConfig.setHardcore(dataView.getInt(HARDCORE).get() > 0);
worldConfig.setDifficulty(DifficultyRegistryModule.get().getByInternalId(dataView.getInt(DIFFICULTY).get())
.orElse(Difficulties.NORMAL));
if (dataView.contains(GENERATOR_NAME)) {
final String genName0 = dataView.getString(GENERATOR_NAME).get();
final String genName = genName0.indexOf(':') == -1 ? "minecraft:" + genName0 : genName0;
final GeneratorType generatorType = Sponge.getRegistry().getType(GeneratorType.class, genName)
.orElse(properties.getDimensionType().getDefaultGeneratorType());
DataContainer generatorSettings = null;
if (dataView.contains(GENERATOR_OPTIONS)) {
String options = dataView.getString(GENERATOR_OPTIONS).get();
String customSettings = null;
if (genName0.equalsIgnoreCase("flat")) {
customSettings = options;
// Added in the lantern-server to allow to attach
// custom generator options to the flat generator
if (dataView.contains(GENERATOR_OPTIONS_EXTRA)) {
options = dataView.getString(GENERATOR_OPTIONS_EXTRA).get();
} else {
options = "";
}
}
if (!options.isEmpty()) {
try {
JsonObject json = GSON.fromJson(options, JsonObject.class);
generatorSettings = JsonTranslator.instance().translate(json).copy();
} catch (Exception e) {
Lantern.getLogger().warn("Unknown generator settings format \"{}\" for type {}, using defaults...",
options, genName);
e.printStackTrace();
}
}
if (generatorSettings == null) {
generatorSettings = generatorType.getGeneratorSettings();
}
if (customSettings != null) {
generatorSettings.set(FlatGeneratorType.SETTINGS, customSettings);
}
} else {
generatorSettings = generatorType.getGeneratorSettings();
}
worldConfig.getGeneration().setGeneratorType(generatorType);
worldConfig.getGeneration().setGeneratorSettings(generatorSettings);
worldConfig.setLowHorizon(generatorType == GeneratorTypes.FLAT);
}
if (spongeDataView != null) {
spongeDataView.getInt(ENABLED).ifPresent(v -> worldConfig.setWorldEnabled(v > 0));
worldConfig.setKeepSpawnLoaded(spongeDataView.getInt(KEEP_SPAWN_LOADED).map(v -> v > 0)
.orElse(properties.getDimensionType().doesKeepSpawnLoaded()));
spongeDataView.getInt(LOAD_ON_STARTUP).ifPresent(v -> worldConfig.setKeepSpawnLoaded(v > 0));
spongeDataView.getStringList(GENERATOR_MODIFIERS).ifPresent(v -> {
final List<String> modifiers = worldConfig.getGeneration().getGenerationModifiers();
modifiers.clear();
modifiers.addAll(v);
properties.updateWorldGenModifiers(modifiers);
});
} else {
final LanternDimensionType dimensionType = properties.getDimensionType();
worldConfig.setKeepSpawnLoaded(dimensionType.doesKeepSpawnLoaded());
worldConfig.setDoesWaterEvaporate(dimensionType.doesWaterEvaporate());
}
}
return properties;
}
static LevelData convert(LanternWorldProperties properties, @Nullable Integer dimensionId, @Nullable BitSet dimensionMap) {
final DataContainer rootContainer = new MemoryDataContainer(DataView.SafetyMode.NO_DATA_CLONED);
final DataView dataView = rootContainer.createView(DATA);
final DataContainer spongeRootContainer = properties.getAdditionalProperties().copy();
final DataView spongeContainer = spongeRootContainer.createView(DataQueries.SPONGE_DATA);
final DataView lanternDataView = spongeContainer.createView(LANTERN);
dataView.set(SEED, properties.getSeed());
dataView.set(VERSION, CURRENT_VERSION);
final DataView rulesView = dataView.createView(GAME_RULES);
for (Entry<String, String> en : properties.getGameRules().entrySet()) {
rulesView.set(DataQuery.of(en.getKey()), en.getValue());
}
// Weather
final WeatherData weatherData = properties.getWeatherData();
final boolean raining = properties.isRaining();
final boolean thunderStorm = properties.isThundering();
dataView.set(RAINING, (byte) (properties.isRaining() ? 1 : 0));
dataView.set(RAIN_TIME, properties.getRainTime());
dataView.set(THUNDERING, (byte) (properties.isThundering() ? 1 : 0));
dataView.set(THUNDER_TIME, properties.getThunderTime());
dataView.set(HARDCORE, (byte) (properties.isHardcore() ? 1 : 0));
dataView.set(CLEAR_WEATHER_TIME, raining || thunderStorm ? 0 : weatherData.getRemainingDuration());
final DataView weatherDataView = lanternDataView.createView(WEATHER);
weatherDataView.set(WEATHER_TYPE, weatherData.getWeather().getId());
weatherDataView.set(WEATHER_REMAINING_DURATION, weatherData.getRemainingDuration());
weatherDataView.set(WEATHER_RUNNING_DURATION, weatherData.getRunningDuration());
// Time
final TimeData timeData = properties.getTimeData();
dataView.set(TIME, timeData.getDayTime());
dataView.set(AGE, timeData.getAge());
lanternDataView.set(MOON_PHASE, timeData.getMoonPhase().toString().toLowerCase());
dataView.set(LAST_PLAYED, properties.getLastPlayedTime());
dataView.set(SIZE_ON_DISK, 0L);
dataView.set(INITIALIZED, (byte) (properties.isInitialized() ? 1 : 0));
String generatorId = properties.getGeneratorType().getId();
if (generatorId.startsWith("minecraft:")) {
generatorId = generatorId.replaceFirst("minecraft:", "");
}
dataView.set(GENERATOR_NAME, generatorId);
// The default world generator has a version of one
dataView.set(GENERATOR_VERSION, generatorId.equalsIgnoreCase("default") ? 1 : 0);
// The flat world generator has a different settings format
if (generatorId.equalsIgnoreCase("flat")) {
dataView.set(GENERATOR_OPTIONS, properties.getGeneratorSettings().getString(FlatGeneratorType.SETTINGS).get());
dataView.set(GENERATOR_OPTIONS_EXTRA, GSON.toJson(JsonTranslator.instance().translate(
properties.getGeneratorSettings().copy().remove(FlatGeneratorType.SETTINGS))));
} else {
dataView.set(GENERATOR_OPTIONS, properties.getGeneratorSettings());
}
dataView.set(DIFFICULTY, ((LanternDifficulty) properties.getDifficulty()).getInternalId());
dataView.set(DIFFICULTY_LOCKED, (byte) (properties.isDifficultyLocked() ? 1 : 0));
dataView.set(GAME_MODE, ((LanternGameMode) properties.getGameMode()).getInternalId());
dataView.set(MAP_FEATURES, (byte) (properties.mapFeatures ? 1 : 0));
dataView.set(BORDER_CENTER_X, properties.borderCenterX);
dataView.set(BORDER_CENTER_Z, properties.borderCenterZ);
dataView.set(BORDER_DAMAGE, properties.borderDamage);
dataView.set(BORDER_DAMAGE_THRESHOLD, properties.borderDamageThreshold);
dataView.set(BORDER_SIZE_END, properties.borderDiameterEnd);
dataView.set(BORDER_SIZE_START, properties.getWorldBorderDiameter());
dataView.set(BORDER_SIZE_LERP_TIME, properties.getWorldBorderTimeRemaining());
dataView.set(BORDER_WARNING_BLOCKS, properties.borderWarningDistance);
dataView.set(BORDER_WARNING_TIME, properties.borderWarningTime);
final Vector3i spawn = properties.getSpawnPosition();
dataView.set(SPAWN_X, spawn.getX());
dataView.set(SPAWN_Y, spawn.getY());
dataView.set(SPAWN_Z, spawn.getZ());
spongeContainer.set(GENERATE_BONUS_CHEST, (byte) (properties.doesGenerateBonusChest() ? 1 : 0));
spongeContainer.set(DIMENSION_TYPE, properties.getDimensionType().getId());
spongeContainer.set(PORTAL_AGENT_TYPE, properties.getPortalAgentType().getId());
spongeContainer.set(GENERATOR_MODIFIERS, properties.generatorModifiers.stream().map(
CatalogType::getId).collect(Collectors.toList()));
// Serialization behavior
short serializationBehaviorId = 1;
if (properties.getSerializationBehavior() == SerializationBehaviors.MANUAL) {
serializationBehaviorId = 0;
} else if (properties.getSerializationBehavior() == SerializationBehaviors.NONE) {
serializationBehaviorId = -1;
}
spongeContainer.set(SERIALIZATION_BEHAVIOR, serializationBehaviorId);
// Tracker
final List<DataView> trackerUniqueIdViews = new ArrayList<>();
final List<UUID> uniqueIdsByIndex = properties.getTrackerIdAllocator().getUniqueIdsByIndex();
for (UUID uniqueId : uniqueIdsByIndex) {
final DataView uniqueIdView = new MemoryDataContainer(DataView.SafetyMode.NO_DATA_CLONED);
if (uniqueId != null) {
uniqueIdView.set(UUID_MOST, uniqueId.getMostSignificantBits());
uniqueIdView.set(UUID_LEAST, uniqueId.getLeastSignificantBits());
}
trackerUniqueIdViews.add(uniqueIdView);
}
spongeContainer.set(TRACKER_UUID_TABLE, trackerUniqueIdViews);
return new LevelData(properties.getWorldName(), properties.getUniqueId(), rootContainer,
spongeRootContainer, dimensionId, dimensionMap);
}
static void write(Path folder, LevelData levelData) throws IOException {
final DataView rootDataView = levelData.worldData;
final DataView dataView = rootDataView.getView(DATA)
.orElseGet(() -> rootDataView.createView(DATA));
dataView.set(NAME, levelData.worldName);
if (levelData.dimensionMap != null) {
final BitSet dimensionMap = levelData.dimensionMap;
final DataView dimensionData = rootDataView.createView(FORGE).createView(DIMENSION_DATA);
final int[] data = new int[(dimensionMap.length() + Integer.SIZE - 1) / Integer.SIZE];
for (int i = 0; i < data.length; i++) {
int val = 0;
for (int j = 0; j < Integer.SIZE; j++) {
val |= dimensionMap.get(i * Integer.SIZE + j) ? (1 << j) : 0;
}
data[i] = val;
}
dimensionData.set(DIMENSION_ARRAY, data);
}
IOHelper.write(folder.resolve(LEVEL_DATA), file -> {
NbtStreamUtils.write(rootDataView, Files.newOutputStream(file), true);
return true;
});
final DataView spongeRootContainer = levelData.spongeWorldData == null ?
new MemoryDataContainer(DataView.SafetyMode.NO_DATA_CLONED) : levelData.spongeWorldData;
final DataView spongeContainer = spongeRootContainer.getView(DataQueries.SPONGE_DATA)
.orElseGet(() -> spongeRootContainer.createView(DataQueries.SPONGE_DATA));
spongeContainer.set(UUID_MOST, levelData.uniqueId.getMostSignificantBits());
spongeContainer.set(UUID_LEAST, levelData.uniqueId.getLeastSignificantBits());
if (levelData.dimensionId != null) {
spongeContainer.set(DIMENSION_INDEX, levelData.dimensionId);
}
IOHelper.write(folder.resolve(SPONGE_LEVEL_DATA), file -> {
NbtStreamUtils.write(spongeRootContainer, Files.newOutputStream(file), true);
return true;
});
}
public static class LevelData {
public final UUID uniqueId;
public final String worldName;
public final DataView worldData;
@Nullable public final DataView spongeWorldData;
// The id of the dimension, if already set before
@Nullable public final Integer dimensionId;
// The map with all the dimension ids, this is only present on the root world (normally)
@Nullable public final BitSet dimensionMap;
public LevelData(String worldName, UUID uniqueId, DataView worldData, @Nullable DataView spongeWorldData, @Nullable Integer dimensionId,
@Nullable BitSet dimensionMap) {
this.spongeWorldData = spongeWorldData;
this.dimensionMap = dimensionMap;
this.dimensionId = dimensionId;
this.uniqueId = uniqueId;
this.worldName = worldName;
this.worldData = worldData;
}
}
}