From 157ccd68cc7f3da6908d3b6a0220c710d9ac4879 Mon Sep 17 00:00:00 2001 From: Woder <17339354+wode490390@users.noreply.github.com> Date: Mon, 26 Feb 2024 22:21:35 +0800 Subject: [PATCH] Add support for Bedrock flat world generator options --- src/main/java/cn/nukkit/level/Level.java | 24 +- .../java/cn/nukkit/level/biome/Biome.java | 4 + .../cn/nukkit/level/format/FullChunk.java | 18 +- .../cn/nukkit/level/format/LevelProvider.java | 3 + .../cn/nukkit/level/format/anvil/Anvil.java | 10 + .../level/format/generic/BaseFullChunk.java | 20 -- .../nukkit/level/format/leveldb/LevelDB.java | 30 +- .../format/leveldb/LevelDbConstants.java | 2 - .../level/format/mcregion/McRegion.java | 10 + .../java/cn/nukkit/level/generator/End.java | 3 +- .../java/cn/nukkit/level/generator/Flat.java | 182 +++-------- .../level/generator/FlatGeneratorOptions.java | 299 ++++++++++++++++++ .../cn/nukkit/level/generator/Generator.java | 2 +- .../level/generator/GeneratorOptions.java | 15 + .../cn/nukkit/level/generator/Nether.java | 2 +- .../cn/nukkit/level/generator/Normal.java | 2 +- .../java/cn/nukkit/level/generator/Void.java | 2 +- 17 files changed, 439 insertions(+), 189 deletions(-) create mode 100644 src/main/java/cn/nukkit/level/generator/FlatGeneratorOptions.java create mode 100644 src/main/java/cn/nukkit/level/generator/GeneratorOptions.java diff --git a/src/main/java/cn/nukkit/level/Level.java b/src/main/java/cn/nukkit/level/Level.java index 79c667e9bd1..5c325756543 100644 --- a/src/main/java/cn/nukkit/level/Level.java +++ b/src/main/java/cn/nukkit/level/Level.java @@ -79,7 +79,6 @@ import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.function.BiConsumer; @@ -299,10 +298,7 @@ public Generator initialValue() { try { Generator generator = generatorClass.getConstructor(Map.class).newInstance(provider.getGeneratorOptions()); NukkitRandom rand = new NukkitRandom(getSeed()); -// if (Server.getInstance().isPrimaryThread()) { -// generator.init(Level.this, rand); -// } - generator.init(new PopChunkManager(getSeed(), getHeightRange()), rand); + generator.init(new PopChunkManager(getSeed(), getHeightRange()), rand, provider.getWorldGeneratorOptions()); return generator; } catch (Throwable e) { Server.getInstance().getLogger().logException(e); @@ -4094,14 +4090,14 @@ public Position getSafeSpawn(Vector3 spawn) { } if (spawn != null) { - Vector3 v = spawn.floor(); - FullChunk chunk = this.getChunk((int) v.x >> 4, (int) v.z >> 4, false); - int x = (int) v.x & 0x0f; - int z = (int) v.z & 0x0f; + BlockVector3 v = spawn.asBlockVector3(); + FullChunk chunk = this.getChunk(v.getChunkX(), v.getChunkZ(), false); + int x = v.x & 0x0f; + int z = v.z & 0x0f; if (chunk != null && chunk.isGenerated()) { - int y = (int) Math.max(Math.min(heightRange.getMaxY() - 1 - 1, v.y), heightRange.getMinY() + 1); - boolean wasAir = chunk.getBlockId(0, x, y - 1, z) == Block.AIR; - for (; y > 0; --y) { + int y = Mth.clamp(Math.min(chunk.getHighestBlockAt(x, z), v.y), heightRange.getMinY() + 1, heightRange.getMaxY() - 1); + boolean wasAir = chunk.getBlockId(0, x, y + 1, z) == Block.AIR; + for (; y > heightRange.getMinY(); --y) { int b = chunk.getFullBlock(0, x, y, z); Block block = Block.fromFullId(b); if (this.isFullBlock(block)) { @@ -4114,14 +4110,14 @@ public Position getSafeSpawn(Vector3 spawn) { } } - for (; y >= heightRange.getMinY() && y < heightRange.getMaxY() - 1; y++) { + for (; y >= heightRange.getMinY() && y <= heightRange.getMaxY(); y++) { int b = chunk.getFullBlock(0, x, y + 1, z); Block block = Block.fromFullId(b); if (!this.isFullBlock(block)) { b = chunk.getFullBlock(0, x, y, z); block = Block.fromFullId(b); if (!this.isFullBlock(block)) { - return new Position(spawn.x, y == (int) spawn.y ? spawn.y : y, spawn.z, this); + return new Position(spawn.x, y, spawn.z, this); } } else { ++y; diff --git a/src/main/java/cn/nukkit/level/biome/Biome.java b/src/main/java/cn/nukkit/level/biome/Biome.java index d5ce73f2230..7295b58930f 100644 --- a/src/main/java/cn/nukkit/level/biome/Biome.java +++ b/src/main/java/cn/nukkit/level/biome/Biome.java @@ -157,6 +157,10 @@ public boolean canSnow() { return false; } + public static int getIdByName(String name) { + return nameToId.getInt(name); + } + @Nullable public static String getNameById(int id) { return idToName[id]; diff --git a/src/main/java/cn/nukkit/level/format/FullChunk.java b/src/main/java/cn/nukkit/level/format/FullChunk.java index 3d4261eaecc..c22dfcef096 100644 --- a/src/main/java/cn/nukkit/level/format/FullChunk.java +++ b/src/main/java/cn/nukkit/level/format/FullChunk.java @@ -76,7 +76,9 @@ default boolean setBlock(int layer, int x, int y, int z, int blockId) { void setBlockLight(int x, int y, int z, int level); - int getHighestBlockAt(int x, int z); + default int getHighestBlockAt(int x, int z) { + return getHighestBlockAt(x, z, true); + } int getHighestBlockAt(int x, int z, boolean cache); @@ -148,15 +150,21 @@ default void fillBiome(int biomeId) { boolean isLoaded(); - boolean load() throws IOException; + default boolean load() throws IOException { + return load(true); + } boolean load(boolean generate) throws IOException; - boolean unload() throws Exception; + default boolean unload() { + return unload(true); + } - boolean unload(boolean save) throws Exception; + default boolean unload(boolean save) { + return unload(save, true); + } - boolean unload(boolean save, boolean safe) throws Exception; + boolean unload(boolean save, boolean safe); void initChunk(); diff --git a/src/main/java/cn/nukkit/level/format/LevelProvider.java b/src/main/java/cn/nukkit/level/format/LevelProvider.java index 70a14793c73..f4aab5c5da4 100644 --- a/src/main/java/cn/nukkit/level/format/LevelProvider.java +++ b/src/main/java/cn/nukkit/level/format/LevelProvider.java @@ -6,6 +6,7 @@ import cn.nukkit.level.format.LevelProviderManager.LevelProviderHandle; import cn.nukkit.level.format.generic.BaseFullChunk; import cn.nukkit.level.generator.Generator; +import cn.nukkit.level.generator.GeneratorOptions; import cn.nukkit.math.Vector3; import cn.nukkit.scheduler.AsyncTask; import it.unimi.dsi.fastutil.longs.Long2ObjectMap; @@ -136,4 +137,6 @@ default void forEachChunks(Function action) { void setSaveChunksOnClose(boolean save); HeightRange getHeightRange(); + + GeneratorOptions getWorldGeneratorOptions(); } diff --git a/src/main/java/cn/nukkit/level/format/anvil/Anvil.java b/src/main/java/cn/nukkit/level/format/anvil/Anvil.java index 68cef6d0a30..9f3db4c9cfb 100644 --- a/src/main/java/cn/nukkit/level/format/anvil/Anvil.java +++ b/src/main/java/cn/nukkit/level/format/anvil/Anvil.java @@ -10,6 +10,8 @@ import cn.nukkit.level.format.generic.BaseLevelProvider; import cn.nukkit.level.format.generic.BaseRegionLoader; import cn.nukkit.level.format.generic.ChunkRequestTask; +import cn.nukkit.level.generator.FlatGeneratorOptions; +import cn.nukkit.level.generator.GeneratorOptions; import cn.nukkit.level.generator.Generators; import cn.nukkit.nbt.NBTIO; import cn.nukkit.nbt.tag.CompoundTag; @@ -37,6 +39,9 @@ public class Anvil extends BaseLevelProvider { public static final Pattern ANVIL_REGEX = Pattern.compile("^r\\.(-?[0-9]+)\\.(-?[0-9]+)\\.mca$"); static final HeightRange DEFAULT_HEIGHT_RANGE = HeightRange.blockY(0, 256); + private static final GeneratorOptions GENERATOR_OPTIONS = GeneratorOptions.builder() + .flatOptions(FlatGeneratorOptions.LEGACY) + .build(); public Anvil(Level level, String path) throws IOException { super(level, path); @@ -310,4 +315,9 @@ public void forEachChunks(Function action, boolean skipCorru public HeightRange getHeightRange() { return DEFAULT_HEIGHT_RANGE; } + + @Override + public GeneratorOptions getWorldGeneratorOptions() { + return GENERATOR_OPTIONS; + } } diff --git a/src/main/java/cn/nukkit/level/format/generic/BaseFullChunk.java b/src/main/java/cn/nukkit/level/format/generic/BaseFullChunk.java index fdf602d17b0..c03d59f43b3 100644 --- a/src/main/java/cn/nukkit/level/format/generic/BaseFullChunk.java +++ b/src/main/java/cn/nukkit/level/format/generic/BaseFullChunk.java @@ -305,11 +305,6 @@ public void populateSkyLight() { } } - @Override - public int getHighestBlockAt(int x, int z) { - return this.getHighestBlockAt(x, z, true); - } - @Override public int getHighestBlockAt(int x, int z, boolean cache) { if (cache) { @@ -404,26 +399,11 @@ public boolean isLoaded() { return this.getProvider() != null && this.getProvider().isChunkLoaded(this.getX(), this.getZ()); } - @Override - public boolean load() throws IOException { - return this.load(true); - } - @Override public boolean load(boolean generate) throws IOException { return this.getProvider() != null && this.getProvider().getChunk(this.getX(), this.getZ(), true) != null; } - @Override - public boolean unload() { - return this.unload(true, true); - } - - @Override - public boolean unload(boolean save) { - return this.unload(save, true); - } - @Override public boolean unload(boolean save, boolean safe) { LevelProvider level = this.getProvider(); diff --git a/src/main/java/cn/nukkit/level/format/leveldb/LevelDB.java b/src/main/java/cn/nukkit/level/format/leveldb/LevelDB.java index bbb93d22bbc..13c333b1eb6 100644 --- a/src/main/java/cn/nukkit/level/format/leveldb/LevelDB.java +++ b/src/main/java/cn/nukkit/level/format/leveldb/LevelDB.java @@ -21,7 +21,9 @@ import cn.nukkit.level.format.anvil.util.NibbleArray; import cn.nukkit.level.format.generic.BaseFullChunk; import cn.nukkit.level.format.generic.ChunkRequestTask; +import cn.nukkit.level.generator.FlatGeneratorOptions; import cn.nukkit.level.generator.Generator; +import cn.nukkit.level.generator.GeneratorOptions; import cn.nukkit.level.generator.Generators; import cn.nukkit.level.util.PalettedSubChunkStorage; import cn.nukkit.math.BlockVector3; @@ -88,6 +90,7 @@ public class LevelDB implements LevelProvider { protected final String path; protected final CompoundTag levelData; + private GeneratorOptions generatorOptions; protected volatile boolean closed; protected final Lock gcLock; @@ -260,7 +263,7 @@ public static void updateLevelData(CompoundTag levelData) { levelData.putByteIfAbsent("isCreatedInEditor", 0); levelData.putByteIfAbsent("isExportedFromEditor", 0); levelData.putStringIfAbsent("BiomeOverride", ""); - levelData.putStringIfAbsent("FlatWorldLayers", DEFAULT_FLAT_WORLD_LAYERS); + levelData.putStringIfAbsent("FlatWorldLayers", FlatGeneratorOptions.DEFAULT_FLAT_WORLD_LAYERS); levelData.putCompoundIfAbsent("world_policies", new CompoundTag()); levelData.putCompoundIfAbsent("experiments", new CompoundTag() .putByte("experiments_ever_used", 0) @@ -1256,6 +1259,7 @@ public CompoundTag getLevelData() { return levelData; } + @Override public void updateLevelName(String name) { if (!this.getName().equals(name)) { this.levelData.putString("LevelName", name); @@ -1428,6 +1432,30 @@ public HeightRange getHeightRange() { return DEFAULT_HEIGHT_RANGE; } + @Override + public GeneratorOptions getWorldGeneratorOptions() { + GeneratorOptions options = this.generatorOptions; + if (options == null) { + String biomeOverride = levelData.getString("BiomeOverride"); + int biome; + if (biomeOverride.isEmpty()) { + biome = -1; + } else { + biome = Biome.getIdByName(biomeOverride); + if (biome != -1 && !Biome.isValidBiome(biome)) { + biome = -1; + } + } + options = GeneratorOptions.builder() + .flatOptions(FlatGeneratorOptions.load(levelData.getString("FlatWorldLayers", FlatGeneratorOptions.DEFAULT_FLAT_WORLD_LAYERS))) + .bonusChest(levelData.getBoolean("bonusChestEnabled")) + .biomeOverride(biome) + .build(); + this.generatorOptions = options; + } + return options; + } + class AutoCompactionTask extends AsyncTask { @Override public void onRun() { diff --git a/src/main/java/cn/nukkit/level/format/leveldb/LevelDbConstants.java b/src/main/java/cn/nukkit/level/format/leveldb/LevelDbConstants.java index 93f8945c861..7d32a748b25 100644 --- a/src/main/java/cn/nukkit/level/format/leveldb/LevelDbConstants.java +++ b/src/main/java/cn/nukkit/level/format/leveldb/LevelDbConstants.java @@ -35,8 +35,6 @@ public final class LevelDbConstants { new IntTag("", 0), // revision new IntTag("", 0))); // beta - public static final String DEFAULT_FLAT_WORLD_LAYERS = "{\"biome_id\":1,\"block_layers\":[{\"block_name\":\"minecraft:bedrock\",\"count\":1},{\"block_name\":\"minecraft:dirt\",\"count\":2},{\"block_name\":\"minecraft:grass\",\"count\":1}],\"encoding_version\":6,\"structure_options\":null,\"world_version\":\"version.post_1_18\"}"; - private LevelDbConstants() { throw new IllegalStateException(); } diff --git a/src/main/java/cn/nukkit/level/format/mcregion/McRegion.java b/src/main/java/cn/nukkit/level/format/mcregion/McRegion.java index 850425d5742..4a4da030afb 100644 --- a/src/main/java/cn/nukkit/level/format/mcregion/McRegion.java +++ b/src/main/java/cn/nukkit/level/format/mcregion/McRegion.java @@ -10,6 +10,8 @@ import cn.nukkit.level.format.generic.BaseFullChunk; import cn.nukkit.level.format.generic.BaseLevelProvider; import cn.nukkit.level.format.generic.BaseRegionLoader; +import cn.nukkit.level.generator.FlatGeneratorOptions; +import cn.nukkit.level.generator.GeneratorOptions; import cn.nukkit.level.generator.Generators; import cn.nukkit.nbt.NBTIO; import cn.nukkit.nbt.tag.CompoundTag; @@ -34,6 +36,9 @@ public class McRegion extends BaseLevelProvider { private static final Pattern REGION_REGEX = Pattern.compile("^r\\.(-?[0-9]+)\\.(-?[0-9]+)\\.mcr$"); static final HeightRange DEFAULT_HEIGHT_RANGE = HeightRange.blockY(0, 128); + private static final GeneratorOptions GENERATOR_OPTIONS = GeneratorOptions.builder() + .flatOptions(FlatGeneratorOptions.LEGACY) + .build(); public McRegion(Level level, String path) throws IOException { super(level, path); @@ -267,4 +272,9 @@ public void forEachChunks(Function action, boolean skipCorru public HeightRange getHeightRange() { return DEFAULT_HEIGHT_RANGE; } + + @Override + public GeneratorOptions getWorldGeneratorOptions() { + return GENERATOR_OPTIONS; + } } diff --git a/src/main/java/cn/nukkit/level/generator/End.java b/src/main/java/cn/nukkit/level/generator/End.java index bbc71bcf7ec..b99c94b6a26 100644 --- a/src/main/java/cn/nukkit/level/generator/End.java +++ b/src/main/java/cn/nukkit/level/generator/End.java @@ -4,7 +4,6 @@ import cn.nukkit.level.Level; import cn.nukkit.level.biome.BiomeID; import cn.nukkit.level.format.generic.BaseFullChunk; -import cn.nukkit.level.generator.populator.type.Populator; import cn.nukkit.math.NukkitRandom; import cn.nukkit.math.Vector3; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; @@ -55,7 +54,7 @@ public Vector3 getSpawn() { } @Override - public void init(ChunkManager level, NukkitRandom random) { + public void init(ChunkManager level, NukkitRandom random, GeneratorOptions generatorOptions) { this.level = level; } diff --git a/src/main/java/cn/nukkit/level/generator/Flat.java b/src/main/java/cn/nukkit/level/generator/Flat.java index ddde4b47aef..4fbed7cf29e 100644 --- a/src/main/java/cn/nukkit/level/generator/Flat.java +++ b/src/main/java/cn/nukkit/level/generator/Flat.java @@ -1,62 +1,38 @@ package cn.nukkit.level.generator; -import cn.nukkit.block.*; import cn.nukkit.level.ChunkManager; -import cn.nukkit.level.format.FullChunk; +import cn.nukkit.level.HeightRange; import cn.nukkit.level.format.generic.BaseFullChunk; -import cn.nukkit.level.generator.object.ore.OreType; +import cn.nukkit.level.generator.FlatGeneratorOptions.BlockLayer; +import cn.nukkit.level.generator.FlatGeneratorOptions.WorldVersion; import cn.nukkit.level.generator.populator.type.Populator; -import cn.nukkit.level.generator.populator.impl.PopulatorOre; import cn.nukkit.math.NukkitRandom; import cn.nukkit.math.Vector3; -import it.unimi.dsi.fastutil.objects.Object2FloatMap; -import it.unimi.dsi.fastutil.objects.Object2FloatOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; -import it.unimi.dsi.fastutil.objects.ObjectArrayList; -import lombok.extern.log4j.Log4j2; -import java.util.List; import java.util.Map; -import java.util.regex.Pattern; /** * author: MagicDroidX * Nukkit Project */ -@Log4j2 public class Flat extends Generator { - - @Override - public int getId() { - return TYPE_FLAT; - } - + private final Map options; private ChunkManager level; - private NukkitRandom random; + private FlatGeneratorOptions flatOptions; - private final List populators = new ObjectArrayList<>(); - - private int[][] structure; - - private final Map options; - - private int floorLevel; - - private String preset; - - private boolean init = false; - - private int biome; + public Flat() { + this(new Object2ObjectOpenHashMap<>(4)); + } - @Override - public ChunkManager getChunkManager() { - return level; + public Flat(Map options) { + this.options = options; } @Override - public Map getSettings() { - return this.options; + public int getId() { + return TYPE_FLAT; } @Override @@ -64,120 +40,43 @@ public String getName() { return "flat"; } - public Flat() { - this(new Object2ObjectOpenHashMap<>(4)); - } - - public Flat(Map options) { - this.preset = "2;7,2x3,2;1;"; - this.options = options; - - if (this.options.containsKey("decoration")) { - PopulatorOre ores = new PopulatorOre( - new OreType(Block.get(BlockID.COAL_ORE), 20, 16, 0, 128), - new OreType(Block.get(BlockID.IRON_ORE), 20, 8, 0, 64), - new OreType(Block.get(BlockID.REDSTONE_ORE), 8, 7, 0, 16), - new OreType(Block.get(BlockID.LAPIS_ORE), 1, 6, 0, 32), - new OreType(Block.get(BlockID.GOLD_ORE), 2, 8, 0, 32), - new OreType(Block.get(BlockID.DIAMOND_ORE), 1, 7, 0, 16), - new OreType(Block.get(BlockID.DIRT), 20, 32, 0, 128), - new OreType(Block.get(BlockID.GRAVEL), 20, 16, 0, 128) - ); - this.populators.add(ores); - } + @Override + public Map getSettings() { + return this.options; } - protected void parsePreset(String preset, int chunkX, int chunkZ) { - try { - this.preset = preset; - String[] presetArray = preset.split(";"); - int version = Integer.parseInt(presetArray[0]); - String blocks = presetArray.length > 1 ? presetArray[1] : ""; - this.biome = presetArray.length > 2 ? Integer.parseInt(presetArray[2]) : 1; - String options = presetArray.length > 3 ? presetArray[1] : ""; - this.structure = new int[256][]; - int y = 0; - for (String block : blocks.split(",")) { - int id, meta = 0, cnt = 1; - if (Pattern.matches("^[0-9]{1,3}x[0-9]$", block)) { - //AxB - String[] s = block.split("x"); - cnt = Integer.parseInt(s[0]); - id = Integer.parseInt(s[1]); - } else if (Pattern.matches("^[0-9]{1,3}:[0-9]{0,2}$", block)) { - //A:B - String[] s = block.split(":"); - id = Integer.parseInt(s[0]); - meta = Integer.parseInt(s[1]); - } else if (Pattern.matches("^[0-9]{1,3}$", block)) { - //A - id = Integer.parseInt(block); - } else { - continue; - } - int cY = y; - y += cnt; - if (y > 0xFF) { - y = 0xFF; - } - for (; cY < y; ++cY) { - this.structure[cY] = new int[]{id, meta}; - } - } - this.floorLevel = y; - for (; y <= 0xFF; ++y) { - this.structure[y] = new int[]{0, 0}; - } - for (String option : options.split(",")) { - if (Pattern.matches("^[0-9a-z_]+$", option)) { - this.options.put(option, true); - } else if (Pattern.matches("^[0-9a-z_]+\\([0-9a-z_ =]+\\)$", option)) { - String name = option.substring(0, option.indexOf("(")); - String extra = option.substring(option.indexOf("(") + 1, option.indexOf(")")); - Object2FloatMap map = new Object2FloatOpenHashMap<>(); - for (String kv : extra.split(" ")) { - String[] data = kv.split("="); - map.put(data[0], Float.parseFloat(data[1])); - } - this.options.put(name, map); - } - } - } catch (Exception e) { - log.error("error while parsing the preset", e); - throw new RuntimeException(e); - } + @Override + public ChunkManager getChunkManager() { + return level; } @Override - public void init(ChunkManager level, NukkitRandom random) { + public void init(ChunkManager level, NukkitRandom random, GeneratorOptions generatorOptions) { this.level = level; this.random = random; + this.flatOptions = generatorOptions.getFlatOptions(); } @Override public void generateChunk(int chunkX, int chunkZ) { - if (!this.init) { - init = true; - Object presetOpt = this.options.get("preset"); - if (presetOpt != null && !"".equals(presetOpt)) { - this.parsePreset((String) presetOpt, chunkX, chunkZ); - } else { - this.parsePreset(this.preset, chunkX, chunkZ); - } - } - this.generateChunk(level.getChunk(chunkX, chunkZ)); - } - - private void generateChunk(FullChunk chunk) { - chunk.setGenerated(); - - for (int Z = 0; Z < 16; ++Z) { - for (int X = 0; X < 16; ++X) { - chunk.setBiomeId(X, Z, biome); + BaseFullChunk chunk = level.getChunk(chunkX, chunkZ); - for (int y = 0; y < 256; ++y) { - int[] pair = this.structure[y]; - chunk.setBlock(0, X, y, Z, pair[0], pair[1]); + chunk.fillBiome(flatOptions.getBiomeId()); + + HeightRange heightRange = chunk.getHeightRange(); + int maxY = heightRange.getMaxY(); + int y = heightRange.getMinY(); + for (BlockLayer layer : flatOptions.getBlockLayers()) { + int blockId = layer.blockId(); + int blockData = layer.blockData(); + for (int i = 0; i < layer.numLayers(); i++) { + for (int x = 0; x < 16; x++) { + for (int z = 0; z < 16; z++) { + chunk.setBlock(0, x, y, z, blockId, blockData); + } + } + if (++y >= maxY) { + return; } } } @@ -186,14 +85,15 @@ private void generateChunk(FullChunk chunk) { @Override public void populateChunk(int chunkX, int chunkZ) { BaseFullChunk chunk = level.getChunk(chunkX, chunkZ); - this.random.setSeed(0xdeadbeef ^ (chunkX << 8) ^ chunkZ ^ this.level.getSeed()); - for (Populator populator : this.populators) { + this.random.setSeed(0xdeadbeefL ^ ((long) chunkX << 8) ^ chunkZ ^ this.level.getSeed()); + for (Populator populator : flatOptions.getStructures()) { populator.populate(this.level, chunkX, chunkZ, this.random, chunk); } } @Override public Vector3 getSpawn() { - return new Vector3(0.5, this.floorLevel, 0.5); + int offset = flatOptions.getWorldVersion() == WorldVersion.POST_1_18 ? -64 : 0; + return new Vector3(0.5, flatOptions.getTotalLayers() + offset, 0.5); } } diff --git a/src/main/java/cn/nukkit/level/generator/FlatGeneratorOptions.java b/src/main/java/cn/nukkit/level/generator/FlatGeneratorOptions.java new file mode 100644 index 00000000000..5040dc986c5 --- /dev/null +++ b/src/main/java/cn/nukkit/level/generator/FlatGeneratorOptions.java @@ -0,0 +1,299 @@ +package cn.nukkit.level.generator; + +import cn.nukkit.block.Block; +import cn.nukkit.block.BlockFullNames; +import cn.nukkit.block.Blocks; +import cn.nukkit.level.biome.Biome; +import cn.nukkit.level.biome.BiomeID; +import cn.nukkit.level.generator.populator.type.Populator; +import cn.nukkit.utils.JsonUtil; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import lombok.Builder; +import lombok.Builder.Default; +import lombok.Value; + +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Builder +@Value +public class FlatGeneratorOptions { + public static final String DEFAULT_FLAT_WORLD_LAYERS = "{\"biome_id\":1,\"block_layers\":[{\"block_name\":\"minecraft:bedrock\",\"count\":1},{\"block_name\":\"minecraft:dirt\",\"count\":2},{\"block_name\":\"minecraft:grass\",\"count\":1}],\"encoding_version\":6,\"structure_options\":null,\"world_version\":\"version.post_1_18\"}"; + public static final String LEGACY_FLAT_WORLD_LAYERS = "{\"biome_id\":1,\"block_layers\":[{\"block_name\":\"minecraft:bedrock\",\"count\":1},{\"block_name\":\"minecraft:dirt\",\"count\":2},{\"block_name\":\"minecraft:grass\",\"count\":1}],\"encoding_version\":6,\"structure_options\":null,\"world_version\":\"version.pre_1_18\"}"; + + private static final int CURRENT_ENCODING_VERSION = 6; + private static final WorldVersion CURRENT_WORLD_VERSION = WorldVersion.POST_1_18; + + public static final FlatGeneratorOptions DEFAULT = load(DEFAULT_FLAT_WORLD_LAYERS); + public static final FlatGeneratorOptions LEGACY = load(LEGACY_FLAT_WORLD_LAYERS); + + private static final BlockLayer AIR_BUFFER = new BlockLayer(Block.AIR, 0, 64); + + int encodingVersion; + int biomeId; + @Default + BlockLayer[] blockLayers = BlockLayer.EMPTY; + @Nullable + JsonNode structureOptions; + @Default + WorldVersion worldVersion = CURRENT_WORLD_VERSION; + + int totalLayers; + @Default + Populator[] structures = new Populator[0]; + + public String save() { + Map root = new HashMap<>(); + root.put("encoding_version", CURRENT_ENCODING_VERSION); + root.put("biome_id", biomeId); + List> blockLayers = new ArrayList<>(); + for (BlockLayer layer : this.blockLayers) { + Map blockLayer = new HashMap<>(); + String blockName = Blocks.getBlockFullNameById(layer.blockId); + if (blockName == null) { + blockName = BlockFullNames.INFO_UPDATE; + } else { + int blockData = layer.blockData; + if (blockData != 0) { + blockLayer.put("block_data", blockData); + } + } + blockLayer.put("block_name", blockName); + blockLayer.put("count", layer.numLayers); + blockLayers.add(blockLayer); + } + root.put("block_layers", blockLayers); + root.put("structure_options", structureOptions); + root.put("world_version", CURRENT_WORLD_VERSION.toString()); + + try { + return JsonUtil.TRUSTED_JSON_MAPPER.writeValueAsString(root); + } catch (JsonProcessingException e) { + return DEFAULT_FLAT_WORLD_LAYERS; + } + } + + public static FlatGeneratorOptions load(String json) { + try { + JsonNode root = JsonUtil.TRUSTED_JSON_MAPPER.readTree(json); + FlatGeneratorOptionsBuilder builder = builder(); + + JsonNode encodingVersionNode = root.get("encoding_version"); + int encodingVersion = encodingVersionNode.asInt(); + builder.encodingVersion(encodingVersion); + + JsonNode biomeIdNode = root.get("biome_id"); + int biomeId = biomeIdNode != null ? Biome.toValidBiome(biomeIdNode.asInt() & 0xff) : BiomeID.PLAINS; + builder.biomeId(biomeId); + + JsonNode structureOptions = root.get("structure_options"); + builder.structureOptions(structureOptions); + //TODO: structures + + JsonNode worldVersionNode = root.get("world_version"); + WorldVersion worldVersion; + if (worldVersionNode != null) { + String version = worldVersionNode.asText(); + worldVersion = WorldVersion.byName(version); + if (worldVersion == null) { // unknown version + worldVersion = WorldVersion.POST_1_18; + } + } else { + worldVersion = WorldVersion.PRE_1_18; + } + builder.worldVersion(worldVersion); + + BlockLayer[] blockLayers; + if (encodingVersion >= 6) { + blockLayers = parseLayersV6(root, worldVersion == WorldVersion.PRE_1_18); + } else if (encodingVersion == 5) { + blockLayers = parseLayersV5(root); + } else if (encodingVersion == 4) { + blockLayers = parseLayersV4(root); + } else { + blockLayers = parseLayersV3(root); + } + builder.blockLayers(blockLayers); + + builder.totalLayers(calculateTotalLayers(blockLayers)); + + return builder.build(); + } catch (Exception e) { + return DEFAULT; + } + } + + private static BlockLayer[] parseLayersV6(JsonNode root, boolean shouldHaveAirBuffer) { + JsonNode blockLayersNode = root.get("block_layers"); + if (blockLayersNode == null) { + return BlockLayer.EMPTY; + } + + List blockLayers = new ArrayList<>(); + for (JsonNode element : blockLayersNode) { + JsonNode countNode = element.get("count"); + int count = countNode != null ? countNode.asInt() : 0; + if (count <= 0) { + continue; + } + + int blockFullId = loadLayerBlock(element); + blockLayers.add(new BlockLayer(Block.getIdFromFullId(blockFullId), Block.getDamageFromFullId(blockFullId), count)); + } + + if (shouldHaveAirBuffer && !blockLayers.isEmpty()) { + BlockLayer bottomLayer = blockLayers.get(0); + if (bottomLayer.blockId != AIR_BUFFER.blockId || bottomLayer.numLayers != AIR_BUFFER.numLayers) { + blockLayers.add(0, AIR_BUFFER); + } + } + + return blockLayers.toArray(BlockLayer.EMPTY); + } + + private static BlockLayer[] parseLayersV5(JsonNode root) { + JsonNode blockLayersNode = root.get("block_layers"); + if (blockLayersNode == null) { + return BlockLayer.EMPTY; + } + + List blockLayers = new ArrayList<>(); + for (JsonNode element : blockLayersNode) { + JsonNode countNode = element.get("count"); + int count = countNode != null ? element.asInt() : 0; + if (count <= 0) { + continue; + } + + int blockFullId = loadLayerBlock(element); + blockLayers.add(new BlockLayer(Block.getIdFromFullId(blockFullId), Block.getDamageFromFullId(blockFullId), count)); + } + + if (!blockLayers.isEmpty()) { + blockLayers.add(0, AIR_BUFFER); + } + + return blockLayers.toArray(BlockLayer.EMPTY); + } + + private static BlockLayer[] parseLayersV4(JsonNode root) { + JsonNode blockLayersNode = root.get("block_layers"); + if (blockLayersNode == null) { + return BlockLayer.EMPTY; + } + + List blockLayers = new ArrayList<>(); + for (JsonNode element : blockLayersNode) { + JsonNode countNode = element.get("count"); + int count = countNode != null ? element.asInt() : 0; + if (count <= 0) { + continue; + } + + int blockId; + int blockData; + JsonNode blockIdNode = element.get("block_id"); + if (blockIdNode != null) { + JsonNode blockDataNode = element.get("block_data"); + blockData = blockDataNode != null ? blockDataNode.asInt() : 0; + + blockId = blockIdNode.asInt(); + try { + Block block = Block.get(blockId, blockData); + blockData = block.getDamage(); + } catch (Exception e) { + blockId = Block.INFO_UPDATE; + blockData = 0; + } + } else { + int blockFullId = loadLayerBlock(element); + blockId = Block.getIdFromFullId(blockFullId); + blockData = Block.getDamageFromFullId(blockFullId); + } + blockLayers.add(new BlockLayer(blockId, blockData, count)); + } + + if (!blockLayers.isEmpty()) { + blockLayers.add(0, AIR_BUFFER); + } + + return blockLayers.toArray(BlockLayer.EMPTY); + } + + private static BlockLayer[] parseLayersV3(JsonNode root) { + return parseLayersV4(root); + } + + private static int loadLayerBlock(JsonNode element) { + int blockId; + int meta; + + JsonNode blockNameNode = element.get("block_name"); + if (blockNameNode != null) { + JsonNode blockDataNode = element.get("block_data"); + int blockData = blockDataNode != null ? blockDataNode.asInt() : 0; + + String blockName = blockNameNode.asText(); + blockId = Blocks.getIdByBlockName(blockName, true); + if (blockId == -1) { + blockId = Block.INFO_UPDATE; + meta = 0; + } else { + Block block = Block.get(blockId, blockData); + meta = block.getDamage(); + } + } else { + blockId = Block.AIR; + meta = 0; + } + + return Block.getFullId(blockId, meta); + } + + public static int calculateTotalLayers(BlockLayer... blockLayers) { + int totalLayers = 0; + for (BlockLayer layer : blockLayers) { + totalLayers += layer.numLayers; + } + return totalLayers; + } + + public record BlockLayer(int blockId, int blockData, int numLayers) { + public static final BlockLayer[] EMPTY = new BlockLayer[0]; + } + + public enum WorldVersion { + PRE_1_18("version.pre_1_18"), + POST_1_18("version.post_1_18"), + ; + + private static final WorldVersion[] VALUES = values(); + private static final Map BY_NAME = new HashMap<>(); + + static { + for (WorldVersion version : VALUES) { + BY_NAME.put(version.name, version); + } + } + + private final String name; + + WorldVersion(String name) { + this.name = name; + } + + @Override + public String toString() { + return name; + } + + @Nullable + public static WorldVersion byName(String name) { + return BY_NAME.get(name); + } + } +} diff --git a/src/main/java/cn/nukkit/level/generator/Generator.java b/src/main/java/cn/nukkit/level/generator/Generator.java index 372bd05f51e..a48c70a9f77 100644 --- a/src/main/java/cn/nukkit/level/generator/Generator.java +++ b/src/main/java/cn/nukkit/level/generator/Generator.java @@ -26,7 +26,7 @@ public int getDimension() { return Level.DIMENSION_OVERWORLD; } - public abstract void init(ChunkManager level, NukkitRandom random); + public abstract void init(ChunkManager level, NukkitRandom random, GeneratorOptions generatorOptions); public abstract void generateChunk(int chunkX, int chunkZ); diff --git a/src/main/java/cn/nukkit/level/generator/GeneratorOptions.java b/src/main/java/cn/nukkit/level/generator/GeneratorOptions.java new file mode 100644 index 00000000000..2bd7b79119c --- /dev/null +++ b/src/main/java/cn/nukkit/level/generator/GeneratorOptions.java @@ -0,0 +1,15 @@ +package cn.nukkit.level.generator; + +import lombok.Builder; +import lombok.Builder.Default; +import lombok.Value; + +@Builder +@Value +public class GeneratorOptions { + @Default + FlatGeneratorOptions flatOptions = FlatGeneratorOptions.DEFAULT; + boolean bonusChest; + @Default + int biomeOverride = -1; +} diff --git a/src/main/java/cn/nukkit/level/generator/Nether.java b/src/main/java/cn/nukkit/level/generator/Nether.java index 5b4807cf0ff..157a646eb64 100644 --- a/src/main/java/cn/nukkit/level/generator/Nether.java +++ b/src/main/java/cn/nukkit/level/generator/Nether.java @@ -70,7 +70,7 @@ public ChunkManager getChunkManager() { } @Override - public void init(ChunkManager level, NukkitRandom random) { + public void init(ChunkManager level, NukkitRandom random, GeneratorOptions generatorOptions) { this.level = level; this.nukkitRandom = random; this.nukkitRandom.setSeed(this.level.getSeed()); diff --git a/src/main/java/cn/nukkit/level/generator/Normal.java b/src/main/java/cn/nukkit/level/generator/Normal.java index 8a7b365efc5..f14d0169eae 100644 --- a/src/main/java/cn/nukkit/level/generator/Normal.java +++ b/src/main/java/cn/nukkit/level/generator/Normal.java @@ -167,7 +167,7 @@ public Biome pickBiome(int x, int z) { } @Override - public void init(ChunkManager level, NukkitRandom random) { + public void init(ChunkManager level, NukkitRandom random, GeneratorOptions generatorOptions) { this.level = level; this.nukkitRandom = random; this.nukkitRandom.setSeed(this.level.getSeed()); diff --git a/src/main/java/cn/nukkit/level/generator/Void.java b/src/main/java/cn/nukkit/level/generator/Void.java index b1c86d48e76..0f9b3e15eb8 100644 --- a/src/main/java/cn/nukkit/level/generator/Void.java +++ b/src/main/java/cn/nukkit/level/generator/Void.java @@ -25,7 +25,7 @@ public int getId() { } @Override - public void init(ChunkManager level, NukkitRandom random) { + public void init(ChunkManager level, NukkitRandom random, GeneratorOptions generatorOptions) { this.level = level; }