From d12d4141ec586f2651f5b039cf83aac173c5f870 Mon Sep 17 00:00:00 2001 From: Lukas Rieger Date: Sat, 30 May 2026 19:25:02 +0200 Subject: [PATCH 1/5] Replace the QuerzNBT with the BlueNBT library --- core/build.gradle.kts | 9 +- .../map/core/util/PackedIntArrayAccess.java | 13 + .../core/world/BlockStateDeserializer.java | 65 ++++ .../java/net/pl3x/map/core/world/Chunk.java | 94 +++--- .../pl3x/map/core/world/ChunkAnvil113.java | 206 ------------- .../pl3x/map/core/world/ChunkAnvil115.java | 209 ------------- .../pl3x/map/core/world/ChunkAnvil116.java | 226 -------------- .../pl3x/map/core/world/ChunkAnvil118.java | 230 -------------- .../net/pl3x/map/core/world/ChunkLoader.java | 133 +++++++++ .../net/pl3x/map/core/world/Chunk_1_13.java | 262 ++++++++++++++++ .../net/pl3x/map/core/world/Chunk_1_15.java | 46 +++ .../net/pl3x/map/core/world/Chunk_1_16.java | 263 ++++++++++++++++ .../net/pl3x/map/core/world/Chunk_1_18.java | 281 ++++++++++++++++++ .../pl3x/map/core/world/CompressionType.java | 41 +++ .../net/pl3x/map/core/world/EmptyChunk.java | 25 +- .../java/net/pl3x/map/core/world/Region.java | 52 +--- gradle/libs.versions.toml | 7 +- 17 files changed, 1199 insertions(+), 963 deletions(-) create mode 100644 core/src/main/java/net/pl3x/map/core/world/BlockStateDeserializer.java delete mode 100644 core/src/main/java/net/pl3x/map/core/world/ChunkAnvil113.java delete mode 100644 core/src/main/java/net/pl3x/map/core/world/ChunkAnvil115.java delete mode 100644 core/src/main/java/net/pl3x/map/core/world/ChunkAnvil116.java delete mode 100644 core/src/main/java/net/pl3x/map/core/world/ChunkAnvil118.java create mode 100644 core/src/main/java/net/pl3x/map/core/world/ChunkLoader.java create mode 100644 core/src/main/java/net/pl3x/map/core/world/Chunk_1_13.java create mode 100644 core/src/main/java/net/pl3x/map/core/world/Chunk_1_15.java create mode 100644 core/src/main/java/net/pl3x/map/core/world/Chunk_1_16.java create mode 100644 core/src/main/java/net/pl3x/map/core/world/Chunk_1_18.java create mode 100644 core/src/main/java/net/pl3x/map/core/world/CompressionType.java diff --git a/core/build.gradle.kts b/core/build.gradle.kts index a5bbcfe67..235e74152 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -23,6 +23,9 @@ repositories { name = "sonatypeSnapshots" mavenContent { snapshotsOnly() } } + maven ( "https://repo.bluecolored.de/releases" ) { + content { includeGroupByRegex ("de\\.bluecolored.*") } + } mavenCentral() maven("https://jitpack.io") } @@ -46,7 +49,7 @@ dependencies { implementation(libs.adventurePlatformFacet) implementation(libs.caffeine) - implementation(libs.querzNbt) + implementation(libs.bluenbt) implementation(libs.lz4Java) implementation(libs.simpleYaml) { exclude("org.yaml", "snakeyaml") @@ -55,6 +58,10 @@ dependencies { // provided by mojang compileOnly(libs.gson) compileOnly(libs.guava) + + // lombok + compileOnly(libs.lombok) + annotationProcessor(libs.lombok) } tasks { diff --git a/core/src/main/java/net/pl3x/map/core/util/PackedIntArrayAccess.java b/core/src/main/java/net/pl3x/map/core/util/PackedIntArrayAccess.java index 5740d6804..2d3b7ebe3 100644 --- a/core/src/main/java/net/pl3x/map/core/util/PackedIntArrayAccess.java +++ b/core/src/main/java/net/pl3x/map/core/util/PackedIntArrayAccess.java @@ -101,6 +101,10 @@ public class PackedIntArrayAccess { private final long maxValue, indexScale, indexOffset; private final int elementsPerLong, indexShift; + public PackedIntArrayAccess(long[] data, int elementCount) { + this(Math.max(data.length * Long.SIZE / elementCount, 1), data); + } + public PackedIntArrayAccess(int bitsPerElement, long[] data) { this.bitsPerElement = bitsPerElement; this.data = data; @@ -127,4 +131,13 @@ private int storageIndex(int i) { return (int) ((long) i * this.indexScale + this.indexOffset >> this.indexShift); } + public int getCapacity() { + return data.length * elementsPerLong; + } + + public boolean isCorrectSize(int expectedSize) { + int capacity = getCapacity(); + return expectedSize <= capacity && expectedSize + elementsPerLong > capacity; + } + } diff --git a/core/src/main/java/net/pl3x/map/core/world/BlockStateDeserializer.java b/core/src/main/java/net/pl3x/map/core/world/BlockStateDeserializer.java new file mode 100644 index 000000000..94950c05b --- /dev/null +++ b/core/src/main/java/net/pl3x/map/core/world/BlockStateDeserializer.java @@ -0,0 +1,65 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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 net.pl3x.map.core.world; + +import de.bluecolored.bluenbt.NBTReader; +import de.bluecolored.bluenbt.TypeDeserializer; +import java.io.IOException; +import java.util.LinkedHashMap; +import java.util.Map; +import net.pl3x.map.core.Pl3xMap; + +public class BlockStateDeserializer implements TypeDeserializer { + + @Override + public BlockState read(NBTReader reader) throws IOException { + reader.beginCompound(); + + String id = null; + Map properties = null; + + while (reader.hasNext()) { + switch (reader.name()) { + case "Name" -> id = reader.nextString(); + case "Properties" -> { + properties = new LinkedHashMap<>(); + reader.beginCompound(); + while (reader.hasNext()) + properties.put(reader.name(), reader.nextString()); + reader.endCompound(); + } + default -> reader.skip(); + } + } + + reader.endCompound(); + + if (id == null) return Blocks.AIR.getDefaultState(); + + Block block = Pl3xMap.api().getBlockRegistry().getOrDefault(id, Blocks.AIR); + return properties == null ? new BlockState(block) : new BlockState(block, properties); + } + +} \ No newline at end of file diff --git a/core/src/main/java/net/pl3x/map/core/world/Chunk.java b/core/src/main/java/net/pl3x/map/core/world/Chunk.java index 4fe589d13..0cc31005a 100644 --- a/core/src/main/java/net/pl3x/map/core/world/Chunk.java +++ b/core/src/main/java/net/pl3x/map/core/world/Chunk.java @@ -23,63 +23,49 @@ */ package net.pl3x.map.core.world; +import de.bluecolored.bluenbt.NBTName; import java.util.LinkedList; import java.util.List; import java.util.Objects; -import java.util.function.Supplier; +import lombok.Getter; import net.pl3x.map.core.util.Colors; -import net.querz.nbt.tag.ByteTag; -import net.querz.nbt.tag.CompoundTag; -import net.querz.nbt.tag.IntTag; -import net.querz.nbt.tag.Tag; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; @NullMarked public abstract class Chunk { + + protected static final int BLOCKS_PER_SECTION = 16 * 16 * 16; + protected static final int VALUES_PER_HEIGHTMAP = 16 * 16; + + protected static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; + protected static final int[] EMPTY_INT_ARRAY = new int[0]; + protected static final long[] EMPTY_LONG_ARRAY = new long[0]; + protected static final String[] EMPTY_STRING_ARRAY = new String[0]; + protected static final BlockState[] EMPTY_BLOCKSTATE_ARRAY = new BlockState[0]; + private final World world; private final Region region; + private final int dataVersion; + private final int xPos; private final int yPos; private final int zPos; - private final long inhabitedTime; - protected final BlockData[] data = new BlockData[256]; protected boolean populated; - protected Chunk(World world, Region region) { + protected Chunk(World world, Region region, Data chunkData, int index) { this.world = world; this.region = region; - this.xPos = 0; - this.yPos = 0; - this.zPos = 0; - - this.inhabitedTime = 0; - } + this.dataVersion = chunkData.dataVersion; - protected Chunk(World world, Region region, CompoundTag tag, int index) { - this.world = world; - this.region = region; - - this.xPos = pos(tag.get("xPos"), () -> (region.getX() << 5) + (index & 31)); - this.yPos = pos(tag.get("yPos"), () -> world.getMinBuildHeight() >> 4); - this.zPos = pos(tag.get("zPos"), () -> (region.getZ() << 5) + (index << 5)); - - this.inhabitedTime = tag.getLong("InhabitedTime"); - } - - private int pos(Tag tag, Supplier failsafe) { - if (tag instanceof IntTag intTag) { - return intTag.asInt(); - } - if (tag instanceof ByteTag byteTag) { - return byteTag.asInt(); - } - return failsafe.get(); + this.xPos = (region.getX() << 5) + (index & 31); + this.yPos = world.getMinBuildHeight() >> 4; + this.zPos = (region.getZ() << 5) + (index << 5); } public World getWorld() { @@ -90,6 +76,10 @@ public Region getRegion() { return this.region; } + public int getDataVersion() { + return dataVersion; + } + public int getX() { return this.xPos; } @@ -102,13 +92,19 @@ public int getZ() { return this.zPos; } - public long getInhabitedTime() { - return this.inhabitedTime; - } + public abstract int getMinY(); + + public abstract int getMaxY(); + + public abstract long getInhabitedTime(); public abstract boolean isFull(); - public abstract boolean noHeightmap(); + public abstract boolean hasWorldSurfaceHeights(); + + public boolean noHeightmap() { + return !hasWorldSurfaceHeights(); + } public abstract int getWorldSurfaceY(int x, int z); @@ -132,7 +128,7 @@ public Chunk populate() { for (int blockZ = startX; blockZ < startX + 16; blockZ++) { for (int blockX = startZ; blockX < startZ + 16; blockX++) { BlockData data = new BlockData(); - data.blockY = noHeightmap() ? getWorld().getMaxBuildHeight() : getWorldSurfaceY(blockX, blockZ) + 1; + data.blockY = noHeightmap() ? getMaxY() : getWorldSurfaceY(blockX, blockZ) + 1; // if world has ceiling iterate down until we find air if (getWorld().hasCeiling()) { @@ -198,18 +194,6 @@ public BlockData[] getData() { return this.data[((z & 0xF) << 4) + (x & 0xF)]; } - public static Chunk create(World world, Region region, CompoundTag tag, int index) { - // https://minecraft.wiki/w/Data_version#List_of_data_versions - int version = tag.getInt("DataVersion"); - Chunk chunk; - if (version < 1519) chunk = new EmptyChunk(world, region); // wtf, older than 1.13 - else if (version < 2200) chunk = new ChunkAnvil113(world, region, tag, index); // 1.13 - 1.14 - else if (version < 2500) chunk = new ChunkAnvil115(world, region, tag, index); // 1.15 - else if (version < 2844) chunk = new ChunkAnvil116(world, region, tag, index); // 1.16 - 1.18 (21w42a) - else chunk = new ChunkAnvil118(world, region, tag, index); // 1.18+ (21w43a+) - return chunk.isFull() ? chunk : new EmptyChunk(world, region); - } - @Override public boolean equals(@Nullable Object o) { if (this == o) { @@ -296,4 +280,14 @@ public List getGlassColors() { return this.glass; } } + + @SuppressWarnings("FieldMayBeFinal") + @Getter + public static class Data { + + @NBTName("DataVersion") + private int dataVersion = 0; + + } + } diff --git a/core/src/main/java/net/pl3x/map/core/world/ChunkAnvil113.java b/core/src/main/java/net/pl3x/map/core/world/ChunkAnvil113.java deleted file mode 100644 index fa399b119..000000000 --- a/core/src/main/java/net/pl3x/map/core/world/ChunkAnvil113.java +++ /dev/null @@ -1,206 +0,0 @@ -/* - * This file is part of BlueMap, licensed under the MIT License (MIT). - * - * Copyright (c) Blue (Lukas Rieger) - * 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 net.pl3x.map.core.world; - -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; -import net.pl3x.map.core.Pl3xMap; -import net.pl3x.map.core.util.MCAMath; -import net.querz.nbt.tag.ByteArrayTag; -import net.querz.nbt.tag.CompoundTag; -import net.querz.nbt.tag.IntArrayTag; -import net.querz.nbt.tag.ListTag; -import net.querz.nbt.tag.StringTag; -import net.querz.nbt.tag.Tag; -import org.jspecify.annotations.NullMarked; - -@NullMarked -public class ChunkAnvil113 extends Chunk { - private Section[] sections = new Section[0]; - - private int[] biomes; - - protected long[] worldSurfaceHeights = new long[0]; - - private final boolean full; - - protected ChunkAnvil113(World world, Region region, CompoundTag chunkTag, int index) { - super(world, region, chunkTag, index); - - CompoundTag levelData = chunkTag.getCompoundTag("Level"); - - this.full = levelData.getString("Status").equals("full"); - if (!this.full) { - return; - } - - if (levelData.containsKey("Heightmaps")) { - CompoundTag heightmaps = levelData.getCompoundTag("Heightmaps"); - this.worldSurfaceHeights = heightmaps.getLongArray("WORLD_SURFACE"); - } - - if (levelData.containsKey("Sections")) { - this.sections = new Section[32]; //32 supports a max world-height of 512 which is the max that the heightmaps of Minecraft V1.13+ can store with 9 bits, I believe? - for (CompoundTag sectionTag : levelData.getListTag("Sections").asCompoundTagList()) { - Section section = new Section(sectionTag); - if (section.sectionY >= 0 && section.sectionY < this.sections.length) { - this.sections[section.sectionY] = section; - } - } - } - - Tag tag = levelData.get("Biomes"); //tag can be byte-array or int-array - if (tag instanceof ByteArrayTag) { - byte[] bs = ((ByteArrayTag) tag).getValue(); - this.biomes = new int[bs.length]; - for (int i = 0; i < bs.length; i++) { - this.biomes[i] = bs[i] & 0xFF; - } - } else if (tag instanceof IntArrayTag) { - this.biomes = ((IntArrayTag) tag).getValue(); - } - if (this.biomes == null || this.biomes.length == 0) { - this.biomes = new int[256]; - } - if (this.biomes.length < 256) { - this.biomes = Arrays.copyOf(this.biomes, 256); - } - } - - @Override - public boolean isFull() { - return this.full; - } - - @Override - public BlockState getBlockState(int x, int y, int z) { - int sectionY = y >> 4; - if (sectionY < 0 || sectionY >= this.sections.length) { - return Blocks.AIR.getDefaultState(); - } - Section section = this.sections[sectionY]; - return section == null ? Blocks.AIR.getDefaultState() : section.getBlockState(x, y, z); - } - - @Override - public int getLight(int x, int y, int z) { - int sectionY = y >> 4; - if (sectionY < 0 || sectionY >= this.sections.length) { - return (y < 0) ? 0 : getWorld().getSkylight(); - } - Section section = this.sections[sectionY]; - return section == null ? getWorld().getSkylight() : section.getLight(x, y, z); - } - - @Override - public Biome getBiome(int x, int y, int z) { - int index = ((z & 0xF) << 4) + (x & 0xF); - if (index >= this.biomes.length) { - return Biome.DEFAULT; - } - Biome biome = LegacyBiomes.get(this.biomes[index]); - return biome == null ? Biome.DEFAULT : biome; - } - - @Override - public boolean noHeightmap() { - return this.worldSurfaceHeights.length < 36; - } - - @Override - public int getWorldSurfaceY(int x, int z) { - if (noHeightmap()) { - return 0; - } - return (int) MCAMath.getValueFromLongStream(this.worldSurfaceHeights, ((z & 0xF) << 4) + (x & 0xF), 9); - } - - protected static class Section { - private final int sectionY; - private byte[] blockLight; - private long[] blocks; - private BlockState[] palette = new BlockState[0]; - private final int bitsPerBlock; - - public Section(CompoundTag sectionData) { - this.sectionY = sectionData.getNumber("Y").intValue(); - this.blockLight = sectionData.getByteArray("BlockLight"); - this.blocks = sectionData.getLongArray("BlockStates"); - - if (this.blocks.length < 256 && this.blocks.length > 0) { - this.blocks = Arrays.copyOf(this.blocks, 256); - } - if (this.blockLight.length < 2048 && this.blockLight.length > 0) { - this.blockLight = Arrays.copyOf(this.blockLight, 2048); - } - - if (sectionData.containsKey("palette")) { - ListTag paletteTag = sectionData.getListTag("palette").asCompoundTagList(); - this.palette = new BlockState[paletteTag.size()]; - for (int i = 0; i < this.palette.length; i++) { - CompoundTag stateTag = paletteTag.get(i); - String id = stateTag.getString("Name"); - Block block = Pl3xMap.api().getBlockRegistry().getOrDefault(id, Blocks.AIR); - Map properties = new HashMap<>(); - CompoundTag propertiesTag = stateTag.getCompoundTag("Properties"); - if (propertiesTag != null) { - for (Map.Entry> property : propertiesTag) { - properties.put(property.getKey().toLowerCase(), ((StringTag) property.getValue()).getValue().toLowerCase()); - } - } - this.palette[i] = new BlockState(block, properties); - } - } - - this.bitsPerBlock = this.blocks.length >> 6; - } - - public BlockState getBlockState(int x, int y, int z) { - if (this.palette.length == 1) { - return this.palette[0]; - } - if (this.blocks.length == 0) { - return Blocks.AIR.getDefaultState(); - } - int index = ((y & 0xF) << 8) + ((z & 0xF) << 4) + (x & 0xF); - long value = MCAMath.getValueFromLongStream(this.blocks, index, this.bitsPerBlock); - if (value >= this.palette.length) { - return Blocks.AIR.getDefaultState(); - } - return this.palette[(int) value]; - } - - public int getLight(int x, int y, int z) { - if (this.blockLight.length == 0) { - return 0; - } - int index = ((y & 0xF) << 8) + ((z & 0xF) << 4) + (x & 0xF); - int half = index >> 1; - boolean upper = (index & 0x1) != 0; - return MCAMath.getByteHalf(this.blockLight[half], upper); - } - } -} diff --git a/core/src/main/java/net/pl3x/map/core/world/ChunkAnvil115.java b/core/src/main/java/net/pl3x/map/core/world/ChunkAnvil115.java deleted file mode 100644 index 499e894f7..000000000 --- a/core/src/main/java/net/pl3x/map/core/world/ChunkAnvil115.java +++ /dev/null @@ -1,209 +0,0 @@ -/* - * This file is part of BlueMap, licensed under the MIT License (MIT). - * - * Copyright (c) Blue (Lukas Rieger) - * 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 net.pl3x.map.core.world; - -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; -import net.pl3x.map.core.Pl3xMap; -import net.pl3x.map.core.util.MCAMath; -import net.querz.nbt.tag.ByteArrayTag; -import net.querz.nbt.tag.CompoundTag; -import net.querz.nbt.tag.IntArrayTag; -import net.querz.nbt.tag.ListTag; -import net.querz.nbt.tag.StringTag; -import net.querz.nbt.tag.Tag; -import org.jspecify.annotations.NullMarked; - -@NullMarked -public class ChunkAnvil115 extends Chunk { - private Section[] sections = new Section[0]; - - private int[] biomes; - - protected long[] worldSurfaceHeights = new long[0]; - - private final boolean full; - - protected ChunkAnvil115(World world, Region region, CompoundTag chunkTag, int index) { - super(world, region, chunkTag, index); - - CompoundTag levelData = chunkTag.getCompoundTag("Level"); - - this.full = levelData.getString("Status").equals("full"); - if (!this.full) { - return; - } - - if (levelData.containsKey("Heightmaps")) { - CompoundTag heightmaps = levelData.getCompoundTag("Heightmaps"); - this.worldSurfaceHeights = heightmaps.getLongArray("WORLD_SURFACE"); - } - - if (levelData.containsKey("Sections")) { - this.sections = new Section[32]; //32 supports a max world-height of 512 which is the max that the heightmaps of Minecraft V1.13+ can store with 9 bits, I believe? - for (CompoundTag sectionTag : levelData.getListTag("Sections").asCompoundTagList()) { - Section section = new Section(sectionTag); - if (section.sectionY >= 0 && section.sectionY < this.sections.length) { - this.sections[section.sectionY] = section; - } - } - } - - Tag tag = levelData.get("Biomes"); //tag can be byte-array or int-array - if (tag instanceof ByteArrayTag) { - byte[] bs = ((ByteArrayTag) tag).getValue(); - this.biomes = new int[bs.length]; - for (int i = 0; i < bs.length; i++) { - this.biomes[i] = bs[i] & 0xFF; - } - } else if (tag instanceof IntArrayTag) { - this.biomes = ((IntArrayTag) tag).getValue(); - } - if (this.biomes == null || this.biomes.length == 0) { - this.biomes = new int[1024]; - } - if (this.biomes.length < 1024) { - this.biomes = Arrays.copyOf(this.biomes, 1024); - } - } - - @Override - public boolean isFull() { - return this.full; - } - - @Override - public BlockState getBlockState(int x, int y, int z) { - int sectionY = y >> 4; - if (sectionY < 0 || sectionY >= this.sections.length) { - return Blocks.AIR.getDefaultState(); - } - Section section = this.sections[sectionY]; - return section == null ? Blocks.AIR.getDefaultState() : section.getBlockState(x, y, z); - } - - @Override - public int getLight(int x, int y, int z) { - int sectionY = y >> 4; - if (sectionY < 0 || sectionY >= this.sections.length) { - return (y < 0) ? 0 : getWorld().getSkylight(); - } - Section section = this.sections[sectionY]; - return section == null ? getWorld().getSkylight() : section.getLight(x, y, z); - } - - @Override - public Biome getBiome(int x, int y, int z) { - int index = ((y >> 2) << 4) + (((z & 0xF) >> 2) << 2) + ((x & 0xF) >> 2); - if (index < 0) { - return Biome.DEFAULT; - } - if (index >= this.biomes.length) { - return Biome.DEFAULT; - } - Biome biome = LegacyBiomes.get(this.biomes[index]); - return biome == null ? Biome.DEFAULT : biome; - } - - @Override - public boolean noHeightmap() { - return this.worldSurfaceHeights.length < 36; - } - - @Override - public int getWorldSurfaceY(int x, int z) { - if (noHeightmap()) { - return 0; - } - return (int) MCAMath.getValueFromLongStream(this.worldSurfaceHeights, ((z & 0xF) << 4) + (x & 0xF), 9); - } - - protected static class Section { - private final int sectionY; - private byte[] blockLight; - private long[] blocks; - private BlockState[] palette = new BlockState[0]; - private final int bitsPerBlock; - - public Section(CompoundTag sectionData) { - this.sectionY = sectionData.getNumber("Y").intValue(); - this.blockLight = sectionData.getByteArray("BlockLight"); - this.blocks = sectionData.getLongArray("BlockStates"); - - if (this.blocks.length < 256 && this.blocks.length > 0) { - this.blocks = Arrays.copyOf(this.blocks, 256); - } - if (this.blockLight.length < 2048 && this.blockLight.length > 0) { - this.blockLight = Arrays.copyOf(this.blockLight, 2048); - } - - if (sectionData.containsKey("palette")) { - ListTag paletteTag = sectionData.getListTag("palette").asCompoundTagList(); - this.palette = new BlockState[paletteTag.size()]; - for (int i = 0; i < this.palette.length; i++) { - CompoundTag stateTag = paletteTag.get(i); - String id = stateTag.getString("Name"); - Block block = Pl3xMap.api().getBlockRegistry().getOrDefault(id, Blocks.AIR); - Map properties = new HashMap<>(); - CompoundTag propertiesTag = stateTag.getCompoundTag("Properties"); - if (propertiesTag != null) { - for (Map.Entry> property : propertiesTag) { - properties.put(property.getKey().toLowerCase(), ((StringTag) property.getValue()).getValue().toLowerCase()); - } - } - this.palette[i] = new BlockState(block, properties); - } - } - - this.bitsPerBlock = this.blocks.length >> 6; - } - - public BlockState getBlockState(int x, int y, int z) { - if (this.palette.length == 1) { - return this.palette[0]; - } - if (this.blocks.length == 0) { - return Blocks.AIR.getDefaultState(); - } - int index = ((y & 0xF) << 8) + ((z & 0xF) << 4) + (x & 0xF); - long value = MCAMath.getValueFromLongStream(this.blocks, index, this.bitsPerBlock); - if (value >= this.palette.length) { - return Blocks.AIR.getDefaultState(); - } - return this.palette[(int) value]; - } - - public int getLight(int x, int y, int z) { - if (this.blockLight.length == 0) { - return 0; - } - int index = ((y & 0xF) << 8) + ((z & 0xF) << 4) + (x & 0xF); - int half = index >> 1; - boolean upper = (index & 0x1) != 0; - return MCAMath.getByteHalf(this.blockLight[half], upper); - } - } -} diff --git a/core/src/main/java/net/pl3x/map/core/world/ChunkAnvil116.java b/core/src/main/java/net/pl3x/map/core/world/ChunkAnvil116.java deleted file mode 100644 index cfcc29a32..000000000 --- a/core/src/main/java/net/pl3x/map/core/world/ChunkAnvil116.java +++ /dev/null @@ -1,226 +0,0 @@ -/* - * This file is part of BlueMap, licensed under the MIT License (MIT). - * - * Copyright (c) Blue (Lukas Rieger) - * 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 net.pl3x.map.core.world; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import net.pl3x.map.core.Pl3xMap; -import net.pl3x.map.core.util.MCAMath; -import net.querz.nbt.tag.ByteArrayTag; -import net.querz.nbt.tag.CompoundTag; -import net.querz.nbt.tag.IntArrayTag; -import net.querz.nbt.tag.ListTag; -import net.querz.nbt.tag.StringTag; -import net.querz.nbt.tag.Tag; -import org.jspecify.annotations.NullMarked; -import org.jspecify.annotations.Nullable; - -@NullMarked -public class ChunkAnvil116 extends Chunk { - private int sectionMin = Integer.MAX_VALUE; - - private Section[] sections = new Section[0]; - - private int[] biomes; - - protected long[] worldSurfaceHeights = new long[0]; - - private final boolean full; - - protected ChunkAnvil116(World world, Region region, CompoundTag chunkTag, int index) { - super(world, region, chunkTag, index); - - CompoundTag levelData = chunkTag.getCompoundTag("Level"); - - this.full = levelData.getString("Status").equals("full"); - if (!this.full) { - return; - } - - if (levelData.containsKey("Heightmaps")) { - CompoundTag heightmaps = levelData.getCompoundTag("Heightmaps"); - this.worldSurfaceHeights = heightmaps.getLongArray("WORLD_SURFACE"); - } - - if (levelData.containsKey("Sections")) { - ListTag sections = levelData.getListTag("Sections").asCompoundTagList(); - List
list = new ArrayList<>(sections.size()); - int sectionMax = Integer.MIN_VALUE; - for (CompoundTag sectionTag : sections) { - if (sectionTag.getListTag("Palette") == null) { - continue; // ignore empty sections - } - Section section = new Section(sectionTag); - int y = section.sectionY; - if (this.sectionMin > y) this.sectionMin = y; - if (sectionMax < y) sectionMax = y; - list.add(section); - } - this.sections = new Section[1 + sectionMax - this.sectionMin]; - for (Section section : list) { - this.sections[section.sectionY - this.sectionMin] = section; - } - } - - Tag tag = levelData.get("Biomes"); //tag can be byte-array or int-array - if (tag instanceof ByteArrayTag) { - byte[] bs = ((ByteArrayTag) tag).getValue(); - this.biomes = new int[bs.length]; - for (int i = 0; i < bs.length; i++) { - this.biomes[i] = bs[i] & 0xFF; - } - } else if (tag instanceof IntArrayTag) { - this.biomes = ((IntArrayTag) tag).getValue(); - } - if (this.biomes == null) { - this.biomes = new int[0]; - } - } - - @Override - public boolean isFull() { - return this.full; - } - - @Override - public BlockState getBlockState(int x, int y, int z) { - int sectionY = y >> 4; - Section section = getSection(sectionY); - return section == null ? Blocks.AIR.getDefaultState() : section.getBlockState(x, y, z); - } - - @Override - public int getLight(int x, int y, int z) { - int sectionY = y >> 4; - Section section = getSection(sectionY); - if (section == null) { - return (sectionY < this.sectionMin) ? 0 : getWorld().getSkylight(); - } - return section.getLight(x, y, z); - } - - @Override - public Biome getBiome(int x, int y, int z) { - if (this.biomes.length < 16) { - return Biome.DEFAULT; - } - int index = ((y >> 2) << 4) + (((z & 0xF) >> 2) << 2) + ((x & 0xF) >> 2); - if (index >= this.biomes.length) { - index -= (((index - this.biomes.length) >> 4) + 1) << 4; - } - if (index < 0) { - index -= (index >> 4) << 4; - } - Biome biome = LegacyBiomes.get(this.biomes[index]); - return biome == null ? Biome.DEFAULT : biome; - } - - @Override - public boolean noHeightmap() { - return this.worldSurfaceHeights.length < 37; - } - - @Override - public int getWorldSurfaceY(int x, int z) { - if (noHeightmap()) { - return getWorld().getMinBuildHeight(); - } - return (int) MCAMath.getValueFromLongArray(this.worldSurfaceHeights, ((z & 0xF) << 4) + (x & 0xF), 9) + getWorld().getMinBuildHeight(); - } - - private @Nullable Section getSection(int y) { - y -= this.sectionMin; - return y < 0 || y >= this.sections.length ? null : this.sections[y]; - } - - protected static class Section { - private final int sectionY; - private byte[] blockLight; - private long[] blocks; - private BlockState[] palette = new BlockState[0]; - private final int bitsPerBlock; - - public Section(CompoundTag sectionData) { - this.sectionY = sectionData.getNumber("Y").intValue(); - this.blockLight = sectionData.getByteArray("BlockLight"); - this.blocks = sectionData.getLongArray("BlockStates"); - - if (this.blocks.length < 256 && this.blocks.length > 0) { - this.blocks = Arrays.copyOf(this.blocks, 256); - } - if (this.blockLight.length < 2048 && this.blockLight.length > 0) { - this.blockLight = Arrays.copyOf(this.blockLight, 2048); - } - - if (sectionData.containsKey("palette")) { - ListTag paletteTag = sectionData.getListTag("palette").asCompoundTagList(); - this.palette = new BlockState[paletteTag.size()]; - for (int i = 0; i < this.palette.length; i++) { - CompoundTag stateTag = paletteTag.get(i); - String id = stateTag.getString("Name"); - Block block = Pl3xMap.api().getBlockRegistry().getOrDefault(id, Blocks.AIR); - Map properties = new HashMap<>(); - CompoundTag propertiesTag = stateTag.getCompoundTag("Properties"); - if (propertiesTag != null) { - for (Map.Entry> property : propertiesTag) { - properties.put(property.getKey().toLowerCase(), ((StringTag) property.getValue()).getValue().toLowerCase()); - } - } - this.palette[i] = new BlockState(block, properties); - } - } - - this.bitsPerBlock = this.blocks.length >> 6; - } - - public BlockState getBlockState(int x, int y, int z) { - if (this.palette.length == 1) { - return this.palette[0]; - } - if (this.blocks.length == 0) { - return Blocks.AIR.getDefaultState(); - } - int index = ((y & 0xF) << 8) + ((z & 0xF) << 4) + (x & 0xF); - long value = MCAMath.getValueFromLongArray(this.blocks, index, this.bitsPerBlock); - if (value >= this.palette.length) { - return Blocks.AIR.getDefaultState(); - } - return this.palette[(int) value]; - } - - public int getLight(int x, int y, int z) { - if (this.blockLight.length == 0) { - return 0; - } - int index = ((y & 0xF) << 8) + ((z & 0xF) << 4) + (x & 0xF); - int half = index >> 1; - boolean upper = (index & 0x1) != 0; - return MCAMath.getByteHalf(this.blockLight[half], upper); - } - } -} diff --git a/core/src/main/java/net/pl3x/map/core/world/ChunkAnvil118.java b/core/src/main/java/net/pl3x/map/core/world/ChunkAnvil118.java deleted file mode 100644 index b8deee42f..000000000 --- a/core/src/main/java/net/pl3x/map/core/world/ChunkAnvil118.java +++ /dev/null @@ -1,230 +0,0 @@ -/* - * This file is part of BlueMap, licensed under the MIT License (MIT). - * - * Copyright (c) Blue (Lukas Rieger) - * 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 net.pl3x.map.core.world; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import net.pl3x.map.core.Pl3xMap; -import net.pl3x.map.core.util.MCAMath; -import net.pl3x.map.core.util.PackedIntArrayAccess; -import net.querz.nbt.tag.CompoundTag; -import net.querz.nbt.tag.ListTag; -import net.querz.nbt.tag.StringTag; -import net.querz.nbt.tag.Tag; -import org.jspecify.annotations.NullMarked; -import org.jspecify.annotations.Nullable; - -@NullMarked -public class ChunkAnvil118 extends Chunk { - private int sectionMin = Integer.MAX_VALUE; - - private Section[] sections = new Section[0]; - - protected long[] worldSurfaceHeights = new long[0]; - - private final boolean full; - - protected ChunkAnvil118(World world, Region region, CompoundTag chunkTag, int index) { - super(world, region, chunkTag, index); - - this.full = chunkTag.getString("Status").endsWith("full"); // 1.20 uses minecraft namespace here - if (!this.full) { - return; - } - - if (chunkTag.containsKey("Heightmaps")) { - CompoundTag heightmaps = chunkTag.getCompoundTag("Heightmaps"); - this.worldSurfaceHeights = heightmaps.getLongArray("WORLD_SURFACE"); - } - - if (chunkTag.containsKey("sections")) { - ListTag sections = chunkTag.getListTag("sections").asCompoundTagList(); - List
list = new ArrayList<>(sections.size()); - int sectionMax = Integer.MIN_VALUE; - for (CompoundTag sectionTag : sections) { - Section section = new Section(world, sectionTag); - int y = section.sectionY; - if (this.sectionMin > y) this.sectionMin = y; - if (sectionMax < y) sectionMax = y; - list.add(section); - } - this.sections = new Section[1 + sectionMax - this.sectionMin]; - for (Section section : list) { - this.sections[section.sectionY - this.sectionMin] = section; - } - } - } - - @Override - public boolean isFull() { - return this.full; - } - - @Override - public BlockState getBlockState(int x, int y, int z) { - int sectionY = y >> 4; - Section section = getSection(sectionY); - return section == null ? Blocks.AIR.getDefaultState() : section.getBlockState(x, y, z); - } - - @Override - public int getLight(int x, int y, int z) { - int sectionY = y >> 4; - Section section = getSection(sectionY); - return section == null ? ((sectionY < this.sectionMin) ? 0 : getWorld().getSkylight()) : section.getLight(x, y, z); - } - - @Override - public Biome getBiome(int x, int y, int z) { - int sectionY = y >> 4; - Section section = getSection(sectionY); - return section == null ? Biome.DEFAULT : section.getBiome(x, y, z); - } - - @Override - public boolean noHeightmap() { - return this.worldSurfaceHeights.length < 37; - } - - @Override - public int getWorldSurfaceY(int x, int z) { - if (noHeightmap()) { - return getWorld().getMinBuildHeight(); - } - return heightmap(getWorld().getDimensionHeight(), this.worldSurfaceHeights).get(((z & 0xF) << 4) + (x & 0xF)); - } - - private @Nullable Section getSection(int y) { - y -= this.sectionMin; - return y < 0 || y >= this.sections.length ? null : this.sections[y]; - } - - private static PackedIntArrayAccess heightmap(int worldHeight, long[] data) { - return new PackedIntArrayAccess(MCAMath.ceilLog2(worldHeight + 1), data); - } - - protected static class Section { - private final int sectionY; - private byte[] blockLight; - private long[] blocks; - private long[] biomes = new long[0]; - private BlockState[] blockPalette = new BlockState[0]; - private Biome[] biomePalette = new Biome[0]; - private final int bitsPerBlock; - private final int bitsPerBiome; - - public Section(World world, CompoundTag sectionData) { - this.sectionY = sectionData.getNumber("Y").intValue(); - this.blockLight = sectionData.getByteArray("BlockLight"); - this.blocks = sectionData.getLongArray("BlockStates"); - - CompoundTag blockStatesTag = sectionData.getCompoundTag("block_states"); - if (blockStatesTag != null) { - this.blocks = blockStatesTag.getLongArray("data"); - if (blockStatesTag.containsKey("palette")) { - ListTag paletteTag = blockStatesTag.getListTag("palette").asCompoundTagList(); - this.blockPalette = new BlockState[paletteTag.size()]; - for (int i = 0; i < this.blockPalette.length; i++) { - CompoundTag entry = paletteTag.get(i); - String id = entry.getString("Name"); - Block block = Pl3xMap.api().getBlockRegistry().getOrDefault(id, Blocks.AIR); - Map properties = new HashMap<>(); - CompoundTag propertiesTag = entry.getCompoundTag("Properties"); - if (propertiesTag != null) { - for (Map.Entry> property : propertiesTag) { - properties.put(property.getKey().toLowerCase(), ((StringTag) property.getValue()).getValue().toLowerCase()); - } - } - this.blockPalette[i] = new BlockState(block, properties); - } - } - } - - CompoundTag biomesTag = sectionData.getCompoundTag("biomes"); - if (biomesTag != null) { - this.biomes = biomesTag.getLongArray("data"); - if (biomesTag.containsKey("palette")) { - ListTag paletteTag = biomesTag.getListTag("palette").asStringTagList(); - this.biomePalette = new Biome[paletteTag.size()]; - for (int i = 0; i < this.biomePalette.length; i++) { - biomePalette[i] = world.getBiomeRegistry().getOrDefault(paletteTag.get(i).getValue(), Biome.DEFAULT); - } - } - } - - if (this.blocks.length < 256 && this.blocks.length > 0) { - this.blocks = Arrays.copyOf(this.blocks, 256); - } - if (this.blockLight.length < 2048 && this.blockLight.length > 0) { - this.blockLight = Arrays.copyOf(this.blockLight, 2048); - } - this.bitsPerBlock = this.blocks.length >> 6; - this.bitsPerBiome = MCAMath.ceilLog2(this.biomePalette.length); - } - - public BlockState getBlockState(int x, int y, int z) { - if (this.blockPalette.length == 1) { - return this.blockPalette[0]; - } - if (this.blocks.length == 0) { - return Blocks.AIR.getDefaultState(); - } - int blockIndex = ((y & 0xF) << 8) + ((z & 0xF) << 4) + (x & 0xF); - long value = MCAMath.getValueFromLongArray(this.blocks, blockIndex, this.bitsPerBlock); - if (value >= this.blockPalette.length) { - return Blocks.AIR.getDefaultState(); - } - return this.blockPalette[(int) value]; - } - - public int getLight(int x, int y, int z) { - if (this.blockLight.length == 0) { - return 0; - } - int blockByteIndex = ((y & 0xF) << 8) + ((z & 0xF) << 4) + (x & 0xF); - int blockHalfByteIndex = blockByteIndex >> 1; - boolean largeHalf = (blockByteIndex & 0x1) != 0; - return MCAMath.getByteHalf(this.blockLight[blockHalfByteIndex], largeHalf); - } - - public Biome getBiome(int x, int y, int z) { - if (this.biomePalette.length == 0) { - return Biome.DEFAULT; - } - if (this.biomePalette.length == 1 || this.biomes.length == 0) { - return this.biomePalette[0]; - } - int biomeIndex = (((y & 0xF) >> 2) << 4) + (((z & 0xF) >> 2) << 2) + ((x & 0xF) >> 2); - long value = MCAMath.getValueFromLongArray(this.biomes, biomeIndex, this.bitsPerBiome); - if (value >= this.biomePalette.length) { - return Biome.DEFAULT; - } - return this.biomePalette[(int) value]; - } - } -} diff --git a/core/src/main/java/net/pl3x/map/core/world/ChunkLoader.java b/core/src/main/java/net/pl3x/map/core/world/ChunkLoader.java new file mode 100644 index 000000000..3e0017c1b --- /dev/null +++ b/core/src/main/java/net/pl3x/map/core/world/ChunkLoader.java @@ -0,0 +1,133 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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 net.pl3x.map.core.world; + +import de.bluecolored.bluenbt.BlueNBT; +import de.bluecolored.bluenbt.NamingStrategy; +import de.bluecolored.bluenbt.TypeToken; +import java.io.BufferedInputStream; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.RandomAccessFile; +import java.util.List; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.jspecify.annotations.Nullable; + +@RequiredArgsConstructor +public class ChunkLoader { + + public static final @Nullable CompressionType[] CHUNK_COMPRESSION_MAP = new CompressionType[255]; + static { + CHUNK_COMPRESSION_MAP[0] = CompressionType.NONE; + CHUNK_COMPRESSION_MAP[1] = CompressionType.GZIP; + CHUNK_COMPRESSION_MAP[2] = CompressionType.DEFLATE; + CHUNK_COMPRESSION_MAP[3] = CompressionType.NONE; + CHUNK_COMPRESSION_MAP[4] = CompressionType.LZ4; + } + + private final static BlueNBT BLUENBT = new BlueNBT(); + static { + BLUENBT.setNamingStrategy(NamingStrategy.lowerCaseWithDelimiter("_")); + BLUENBT.register(TypeToken.of(BlockState.class), new BlockStateDeserializer()); + } + + private final World world; + private final Region region; + + // sorted list of chunk-versions, loaders at the start of the list are preferred over loaders at the end + private static final List> CHUNK_VERSION_LOADERS = List.of( + new ChunkVersionLoader<>(Chunk_1_18.Data.class, Chunk_1_18::new, 2844), + new ChunkVersionLoader<>(Chunk_1_16.Data.class, Chunk_1_16::new, 2500), + new ChunkVersionLoader<>(Chunk_1_15.Data.class, Chunk_1_15::new, 2200), + new ChunkVersionLoader<>(Chunk_1_13.Data.class, Chunk_1_13::new, 1519) + ); + + private ChunkVersionLoader lastUsedLoader = CHUNK_VERSION_LOADERS.getFirst(); + + public Chunk load(RandomAccessFile raf, long offset, int index) throws IOException { + raf.seek(offset + 4); + int compressionTypeId = Byte.toUnsignedInt(raf.readByte()); + + CompressionType compression = CHUNK_COMPRESSION_MAP[compressionTypeId]; + if (compression == null) + throw new IOException("Unknown chunk compression-id: " + compressionTypeId); + + // optimistic: try last used version + ChunkVersionLoader usedLoader = lastUsedLoader; + Chunk chunk; + InputStream decompressedIn = new BufferedInputStream(compression.decompress(new FileInputStream(raf.getFD()))); + chunk = usedLoader.load(world, region, decompressedIn, index); + + // check version and reload chunk if the wrong loader has been used and a better one has been found + ChunkVersionLoader actualLoader = findBestLoaderForVersion(chunk.getDataVersion()); + if (actualLoader != null && usedLoader != actualLoader) { + raf.seek(offset + 5); + decompressedIn = new BufferedInputStream(compression.decompress(new FileInputStream(raf.getFD()))); + chunk = actualLoader.load(world, region, decompressedIn, index); + lastUsedLoader = actualLoader; + } + + return chunk.isFull() ? chunk : new EmptyChunk(world, region, index); + } + + private @Nullable ChunkVersionLoader findBestLoaderForVersion(int version) { + for (ChunkVersionLoader loader : CHUNK_VERSION_LOADERS) { + if (loader.mightSupport(version)) return loader; + } + return null; + } + + @RequiredArgsConstructor + @Getter + private static class ChunkVersionLoader { + + private final Class dataType; + private final ChunkConstructor constructor; + private final int dataVersion; + + public Chunk load(World world, Region region, InputStream in, int index) throws IOException { + try { + D data = BLUENBT.read(in, dataType); + return mightSupport(data.getDataVersion()) ? constructor.create(world, region, data, index) : new EmptyChunk(world, region, index); + } catch (Exception e) { + throw new IOException("Failed to parse chunk-data (%s): %s".formatted(dataType.getSimpleName(), e), e); + } + } + + public boolean mightSupport(int dataVersion) { + return dataVersion >= this.dataVersion; + } + + } + + private interface ChunkConstructor { + + Chunk create(World world, Region region, D data, int index); + + } + +} diff --git a/core/src/main/java/net/pl3x/map/core/world/Chunk_1_13.java b/core/src/main/java/net/pl3x/map/core/world/Chunk_1_13.java new file mode 100644 index 000000000..5b217631c --- /dev/null +++ b/core/src/main/java/net/pl3x/map/core/world/Chunk_1_13.java @@ -0,0 +1,262 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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 net.pl3x.map.core.world; + +import de.bluecolored.bluenbt.NBTName; +import lombok.Getter; +import net.pl3x.map.core.util.MCAMath; +import org.jetbrains.annotations.Nullable; + +public class Chunk_1_13 extends Chunk { + + private final boolean full; + private final long inhabitedTime; + + private final boolean hasWorldSurfaceHeights; + private final long[] worldSurfaceHeights; + + private final Section[] sections; + private final int sectionMin, sectionMax; + + final int[] biomes; + + public Chunk_1_13(World world, Region region, Data data, int index) { + super(world, region, data, index); + + Level level = data.level; + + this.full = level.status.equals("full"); + this.inhabitedTime = level.inhabitedTime; + + this.worldSurfaceHeights = level.heightmaps.worldSurface; + + this.hasWorldSurfaceHeights = this.worldSurfaceHeights.length >= 36; + + this.biomes = level.biomes; + + SectionData[] sectionsData = level.sections; + if (sectionsData != null && sectionsData.length > 0) { + int min = Integer.MAX_VALUE; + int max = Integer.MIN_VALUE; + + // find section min/max y + for (SectionData sectionData : sectionsData) { + int y = sectionData.getY(); + if (min > y) min = y; + if (max < y) max = y; + } + + // load sections into ordered array + this.sections = new Section[1 + max - min]; + for (SectionData sectionData : sectionsData) { + Section section = new Section(sectionData); + int y = section.getSectionY(); + + if (min > y) min = y; + if (max < y) max = y; + + this.sections[section.sectionY - min] = section; + } + + this.sectionMin = min; + this.sectionMax = max; + } else { + this.sections = new Section[0]; + this.sectionMin = 0; + this.sectionMax = 0; + } + } + + @Override + public boolean isFull() { + return full; + } + + @Override + public long getInhabitedTime() { + return inhabitedTime; + } + + @Override + public BlockState getBlockState(int x, int y, int z) { + int sectionY = y >> 4; + Section section = getSection(sectionY); + return section == null ? Blocks.AIR.getDefaultState() : section.getBlockState(x, y, z); + } + + @Override + public Biome getBiome(int x, int y, int z) { + if (this.biomes.length < 256) return Biome.DEFAULT; + int biomeIntIndex = (z & 0xF) << 4 | x & 0xF; + return LegacyBiomes.get(biomes[biomeIntIndex]); + } + + @Override + public int getLight(int x, int y, int z) { + int sectionY = y >> 4; + Section section = getSection(sectionY); + return section == null ? ((sectionY < this.sectionMin) ? 0 : getWorld().getSkylight()) : section.getLight(x, y, z); + } + + @Override + public int getMinY() { + return sectionMin * 16; + } + + @Override + public int getMaxY() { + return sectionMax * 16 + 15; + } + + @Override + public boolean hasWorldSurfaceHeights() { + return hasWorldSurfaceHeights; + } + + @Override + public int getWorldSurfaceY(int x, int z) { + return (int) MCAMath.getValueFromLongStream( + worldSurfaceHeights, + (z & 0xF) << 4 | x & 0xF, + 9 + ); + } + + private @Nullable Section getSection(int y) { + y -= sectionMin; + if (y < 0 || y >= this.sections.length) return null; + return this.sections[y]; + } + + protected static class Section { + + private final int sectionY; + private final BlockState[] blockPalette; + private final long[] blocks; + private final byte[] blockLight; + + private final int bitsPerBlock; + + public Section(SectionData sectionData) { + this.sectionY = sectionData.y; + + this.blockPalette = sectionData.palette; + this.blocks = sectionData.blockStates; + + this.blockLight = sectionData.getBlockLight(); + + this.bitsPerBlock = this.blocks.length >> 6; // available longs * 64 (bits per long) / 4096 (blocks per section) (floored result) + } + + public BlockState getBlockState(int x, int y, int z) { + if (blockPalette.length == 1) return blockPalette[0]; + if (blockPalette.length == 0) return Blocks.AIR.getDefaultState(); + + int id = (int) MCAMath.getValueFromLongStream( + blocks, + (y & 0xF) << 8 | (z & 0xF) << 4 | x & 0xF, + bitsPerBlock + ); + if (id >= blockPalette.length) { + return Blocks.AIR.getDefaultState(); + } + + return blockPalette[id]; + } + + public int getLight(int x, int y, int z) { + if (blockLight.length == 0) return 0; + + int blockByteIndex = (y & 0xF) << 8 | (z & 0xF) << 4 | x & 0xF; + int blockHalfByteIndex = blockByteIndex >> 1; + boolean largeHalf = (blockByteIndex & 0x1) != 0; + + return MCAMath.getByteHalf(this.blockLight[blockHalfByteIndex], largeHalf); + } + + public int getSectionY() { + return sectionY; + } + + } + + @Getter + @SuppressWarnings("FieldMayBeFinal") + public static class Data extends Chunk.Data { + + @NBTName("Level") + private Level level = new Level(); + + } + + @Getter + @SuppressWarnings("FieldMayBeFinal") + public static class Level { + + @NBTName("Status") + private String status = "empty"; + + @NBTName("InhabitedTime") + private long inhabitedTime = 0; + + @NBTName("Heightmaps") + private HeightmapsData heightmaps = new HeightmapsData(); + + @NBTName("Sections") + private SectionData @Nullable [] sections = null; + + @NBTName("Biomes") + private int[] biomes = EMPTY_INT_ARRAY; + + } + + @Getter + @SuppressWarnings("FieldMayBeFinal") + public static class HeightmapsData { + + @NBTName("WORLD_SURFACE") + private long[] worldSurface = EMPTY_LONG_ARRAY; + + } + + @Getter + @SuppressWarnings("FieldMayBeFinal") + public static class SectionData { + + @NBTName("Y") + private int y = 0; + + @NBTName("BlockLight") + private byte[] blockLight = EMPTY_BYTE_ARRAY; + + @NBTName("Palette") + private BlockState[] palette = EMPTY_BLOCKSTATE_ARRAY; + + @NBTName("BlockStates") + private long[] blockStates = EMPTY_LONG_ARRAY; + + } + +} diff --git a/core/src/main/java/net/pl3x/map/core/world/Chunk_1_15.java b/core/src/main/java/net/pl3x/map/core/world/Chunk_1_15.java new file mode 100644 index 000000000..5348287e1 --- /dev/null +++ b/core/src/main/java/net/pl3x/map/core/world/Chunk_1_15.java @@ -0,0 +1,46 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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 net.pl3x.map.core.world; + +public class Chunk_1_15 extends Chunk_1_13 { + + public Chunk_1_15(World world, Region region, Data data, int index) { + super(world, region, data, index); + } + + @Override + public Biome getBiome(int x, int y, int z) { + if (this.biomes.length < 16) return Biome.DEFAULT; + + int biomeIntIndex = (y & 0b1100) << 2 | z & 0b1100 | (x & 0b1100) >> 2; + + // shift y up/down if not in range + if (biomeIntIndex >= biomes.length) biomeIntIndex -= (((biomeIntIndex - biomes.length) >> 4) + 1) * 16; + if (biomeIntIndex < 0) biomeIntIndex -= (biomeIntIndex >> 4) * 16; + + return LegacyBiomes.get(biomes[biomeIntIndex]); + } + +} diff --git a/core/src/main/java/net/pl3x/map/core/world/Chunk_1_16.java b/core/src/main/java/net/pl3x/map/core/world/Chunk_1_16.java new file mode 100644 index 000000000..33ce02332 --- /dev/null +++ b/core/src/main/java/net/pl3x/map/core/world/Chunk_1_16.java @@ -0,0 +1,263 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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 net.pl3x.map.core.world; + +import de.bluecolored.bluenbt.NBTName; +import lombok.Getter; +import net.pl3x.map.core.util.MCAMath; +import net.pl3x.map.core.util.PackedIntArrayAccess; +import org.jetbrains.annotations.Nullable; + +public class Chunk_1_16 extends Chunk { + + private final boolean full; + private long inhabitedTime; + + private boolean hasWorldSurfaceHeights; + private PackedIntArrayAccess worldSurfaceHeights; + + private Section[] sections; + private int sectionMin, sectionMax; + + private int[] biomes; + + public Chunk_1_16(World world, Region region, Data data, int index) { + super(world, region, data, index); + + Level level = data.level; + + this.full = level.status.equals("full"); + if (!this.full) { + return; + } + + this.inhabitedTime = level.inhabitedTime; + + int worldHeight = world.getDimensionHeight(); + int bitsPerHeightmapElement = MCAMath.ceilLog2(worldHeight + 1); + + this.worldSurfaceHeights = new PackedIntArrayAccess(bitsPerHeightmapElement, level.heightmaps.worldSurface); + this.hasWorldSurfaceHeights = this.worldSurfaceHeights.isCorrectSize(VALUES_PER_HEIGHTMAP); + + this.biomes = level.biomes; + + SectionData[] sectionsData = level.sections; + if (sectionsData != null && sectionsData.length > 0) { + int min = Integer.MAX_VALUE; + int max = Integer.MIN_VALUE; + + // find section min/max y + for (SectionData sectionData : sectionsData) { + int y = sectionData.getY(); + if (min > y) min = y; + if (max < y) max = y; + } + + // load sections into ordered array + this.sections = new Section[1 + max - min]; + for (SectionData sectionData : sectionsData) { + Section section = new Section(sectionData); + int y = section.getSectionY(); + + if (min > y) min = y; + if (max < y) max = y; + + this.sections[section.sectionY - min] = section; + } + + this.sectionMin = min; + this.sectionMax = max; + } else { + this.sections = new Section[0]; + this.sectionMin = 0; + this.sectionMax = 0; + } + } + + @Override + public boolean isFull() { + return full; + } + + @Override + public long getInhabitedTime() { + return inhabitedTime; + } + + @Override + public BlockState getBlockState(int x, int y, int z) { + int sectionY = y >> 4; + Section section = getSection(sectionY); + return section == null ? Blocks.AIR.getDefaultState() : section.getBlockState(x, y, z); + } + + @Override + public Biome getBiome(int x, int y, int z) { + if (this.biomes.length < 16) return Biome.DEFAULT; + + int biomeIntIndex = (y & 0b1100) << 2 | z & 0b1100 | (x & 0b1100) >> 2; + + // shift y up/down if not in range + if (biomeIntIndex >= biomes.length) biomeIntIndex -= (((biomeIntIndex - biomes.length) >> 4) + 1) * 16; + if (biomeIntIndex < 0) biomeIntIndex -= (biomeIntIndex >> 4) * 16; + + return LegacyBiomes.get(biomes[biomeIntIndex]); + } + + @Override + public int getLight(int x, int y, int z) { + int sectionY = y >> 4; + Section section = getSection(sectionY); + return section == null ? ((sectionY < this.sectionMin) ? 0 : getWorld().getSkylight()) : section.getLight(x, y, z); + } + + @Override + public int getMinY() { + return sectionMin * 16; + } + + @Override + public int getMaxY() { + return sectionMax * 16 + 15; + } + + @Override + public boolean hasWorldSurfaceHeights() { + return hasWorldSurfaceHeights; + } + + @Override + public int getWorldSurfaceY(int x, int z) { + return worldSurfaceHeights.get((z & 0xF) << 4 | x & 0xF); + } + + private @Nullable Section getSection(int y) { + y -= sectionMin; + if (y < 0 || y >= this.sections.length) return null; + return this.sections[y]; + } + + protected static class Section { + + private final int sectionY; + private final BlockState[] blockPalette; + private final PackedIntArrayAccess blocks; + private final byte[] blockLight; + + public Section(SectionData sectionData) { + this.sectionY = sectionData.y; + + this.blockPalette = sectionData.palette; + this.blocks = new PackedIntArrayAccess(sectionData.blockStates, BLOCKS_PER_SECTION); + + this.blockLight = sectionData.getBlockLight(); + } + + public BlockState getBlockState(int x, int y, int z) { + if (blockPalette.length == 1) return blockPalette[0]; + if (blockPalette.length == 0) return Blocks.AIR.getDefaultState(); + + int id = blocks.get((y & 0xF) << 8 | (z & 0xF) << 4 | x & 0xF); + if (id >= blockPalette.length) { + return Blocks.AIR.getDefaultState(); + } + + return blockPalette[id]; + } + + public int getLight(int x, int y, int z) { + if (blockLight.length == 0) return 0; + + int blockByteIndex = (y & 0xF) << 8 | (z & 0xF) << 4 | x & 0xF; + int blockHalfByteIndex = blockByteIndex >> 1; // blockByteIndex / 2 + boolean largeHalf = (blockByteIndex & 0x1) != 0; // (blockByteIndex % 2) == 0 + + return MCAMath.getByteHalf(this.blockLight[blockHalfByteIndex], largeHalf); + } + + public int getSectionY() { + return sectionY; + } + + } + + @Getter + @SuppressWarnings("FieldMayBeFinal") + public static class Data extends Chunk.Data { + + @NBTName("Level") + private Level level = new Level(); + + } + + @Getter + @SuppressWarnings("FieldMayBeFinal") + public static class Level { + + @NBTName("Status") + private String status = "empty"; + + @NBTName("InhabitedTime") + private long inhabitedTime = 0; + + @NBTName("Heightmaps") + private HeightmapsData heightmaps = new HeightmapsData(); + + @NBTName("Sections") + private SectionData @Nullable [] sections = null; + + @NBTName("Biomes") + private int[] biomes = EMPTY_INT_ARRAY; + + } + + @Getter + @SuppressWarnings("FieldMayBeFinal") + public static class HeightmapsData { + + @NBTName("WORLD_SURFACE") + private long[] worldSurface = EMPTY_LONG_ARRAY; + + } + + @Getter + @SuppressWarnings("FieldMayBeFinal") + public static class SectionData { + + @NBTName("Y") + private int y = 0; + + @NBTName("BlockLight") + private byte[] blockLight = EMPTY_BYTE_ARRAY; + + @NBTName("Palette") + private BlockState[] palette = EMPTY_BLOCKSTATE_ARRAY; + + @NBTName("BlockStates") + private long[] blockStates = EMPTY_LONG_ARRAY; + + } + +} diff --git a/core/src/main/java/net/pl3x/map/core/world/Chunk_1_18.java b/core/src/main/java/net/pl3x/map/core/world/Chunk_1_18.java new file mode 100644 index 000000000..1d09570c3 --- /dev/null +++ b/core/src/main/java/net/pl3x/map/core/world/Chunk_1_18.java @@ -0,0 +1,281 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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 net.pl3x.map.core.world; + +import de.bluecolored.bluenbt.NBTName; +import lombok.Getter; +import net.pl3x.map.core.util.MCAMath; +import net.pl3x.map.core.util.PackedIntArrayAccess; +import org.jetbrains.annotations.Nullable; + +public class Chunk_1_18 extends Chunk { + private final boolean full; + private long inhabitedTime; + + private int worldMinY; + + private boolean hasWorldSurfaceHeights; + private PackedIntArrayAccess worldSurfaceHeights; + + private Section[] sections; + private int sectionMin, sectionMax; + + public Chunk_1_18(World world, Region region, Data data, int index) { + super(world, region, data, index); + + this.full = data.status.endsWith("full"); // 1.20 uses minecraft namespace here + if (!this.full) { + return; + } + + this.inhabitedTime = data.inhabitedTime; + + this.worldMinY = world.getMinBuildHeight(); + + int worldHeight = world.getDimensionHeight(); + int bitsPerHeightmapElement = MCAMath.ceilLog2(worldHeight + 1); + + this.worldSurfaceHeights = new PackedIntArrayAccess(bitsPerHeightmapElement, data.heightmaps.worldSurface); + this.hasWorldSurfaceHeights = this.worldSurfaceHeights.isCorrectSize(VALUES_PER_HEIGHTMAP); + + SectionData[] sectionsData = data.sections; + if (sectionsData != null && sectionsData.length > 0) { + int min = Integer.MAX_VALUE; + int max = Integer.MIN_VALUE; + + // find section min/max y + for (SectionData sectionData : sectionsData) { + int y = sectionData.getY(); + if (min > y) min = y; + if (max < y) max = y; + } + + // load sections into ordered array + this.sections = new Section[1 + max - min]; + for (SectionData sectionData : sectionsData) { + Section section = new Section(getWorld(), sectionData); + int y = section.getSectionY(); + + if (min > y) min = y; + if (max < y) max = y; + + this.sections[section.sectionY - min] = section; + } + + this.sectionMin = min; + this.sectionMax = max; + } else { + this.sections = new Section[0]; + this.sectionMin = 0; + this.sectionMax = 0; + } + + } + + @Override + public boolean isFull() { + return full; + } + + @Override + public long getInhabitedTime() { + return inhabitedTime; + } + + @Override + public BlockState getBlockState(int x, int y, int z) { + int sectionY = y >> 4; + Section section = getSection(sectionY); + return section == null ? Blocks.AIR.getDefaultState() : section.getBlockState(x, y, z); + } + + @Override + public Biome getBiome(int x, int y, int z) { + int sectionY = y >> 4; + Section section = getSection(sectionY); + return section == null ? Biome.DEFAULT : section.getBiome(x, y, z); + } + + @Override + public int getLight(int x, int y, int z) { + int sectionY = y >> 4; + Section section = getSection(sectionY); + return section == null ? ((sectionY < this.sectionMin) ? 0 : getWorld().getSkylight()) : section.getLight(x, y, z); + } + + @Override + public int getMinY() { + return sectionMin * 16; + } + + @Override + public int getMaxY() { + return sectionMax * 16 + 15; + } + + @Override + public boolean hasWorldSurfaceHeights() { + return hasWorldSurfaceHeights; + } + + @Override + public int getWorldSurfaceY(int x, int z) { + return worldSurfaceHeights.get((z & 0xF) << 4 | x & 0xF) + worldMinY; + } + + private @Nullable Section getSection(int y) { + y -= sectionMin; + if (y < 0 || y >= this.sections.length) return null; + return this.sections[y]; + } + + protected static class Section { + + private final int sectionY; + private final BlockState[] blockPalette; + private final Biome[] biomePalette; + private final PackedIntArrayAccess blocks; + private final PackedIntArrayAccess biomes; + private final byte[] blockLight; + + public Section(World world, SectionData sectionData) { + this.sectionY = sectionData.y; + + this.blockPalette = sectionData.blockStates.palette; + + this.biomePalette = new Biome[sectionData.biomes.palette.length]; + for (int i = 0; i < this.biomePalette.length; i++) { + this.biomePalette[i] = world.getBiomeRegistry().getOrDefault(sectionData.biomes.palette[i], Biome.DEFAULT); + } + + this.blocks = new PackedIntArrayAccess(sectionData.blockStates.data, BLOCKS_PER_SECTION); + this.biomes = new PackedIntArrayAccess(Math.max(MCAMath.ceilLog2(this.biomePalette.length), 1), sectionData.biomes.data); + + this.blockLight = sectionData.blockLight; + } + + public BlockState getBlockState(int x, int y, int z) { + if (blockPalette.length == 1) return blockPalette[0]; + if (blockPalette.length == 0) return Blocks.AIR.getDefaultState(); + + int id = blocks.get((y & 0xF) << 8 | (z & 0xF) << 4 | x & 0xF); + if (id >= blockPalette.length) { + return Blocks.AIR.getDefaultState(); + } + + return blockPalette[id]; + } + + public Biome getBiome(int x, int y, int z) { + if (biomePalette.length == 1) return biomePalette[0]; + if (biomePalette.length == 0) return Biome.DEFAULT; + + int id = biomes.get((y & 0b1100) << 2 | z & 0b1100 | (x & 0b1100) >> 2); + if (id >= biomePalette.length) { + return Biome.DEFAULT; + } + + return biomePalette[id]; + } + + public int getLight(int x, int y, int z) { + if (blockLight.length == 0) return 0; + + int blockByteIndex = (y & 0xF) << 8 | (z & 0xF) << 4 | x & 0xF; + int blockHalfByteIndex = blockByteIndex >> 1; // blockByteIndex / 2 + boolean largeHalf = (blockByteIndex & 0x1) != 0; // (blockByteIndex % 2) == 0 + + return MCAMath.getByteHalf(this.blockLight[blockHalfByteIndex], largeHalf); + } + + public int getSectionY() { + return sectionY; + } + + } + + @Getter + @SuppressWarnings("FieldMayBeFinal") + public static class Data extends Chunk.Data { + + @NBTName("Status") + private String status = "minecraft:empty"; + + @NBTName("InhabitedTime") + private long inhabitedTime = 0; + + @NBTName("Heightmaps") + private HeightmapsData heightmaps = new HeightmapsData(); + + private SectionData @Nullable [] sections = null; + + } + + @Getter + @SuppressWarnings("FieldMayBeFinal") + public static class HeightmapsData { + + @NBTName("WORLD_SURFACE") + private long[] worldSurface = EMPTY_LONG_ARRAY; + + } + + @Getter + @SuppressWarnings("FieldMayBeFinal") + public static class SectionData { + + @NBTName("Y") + private int y = 0; + + @NBTName("BlockLight") + private byte[] blockLight = EMPTY_BYTE_ARRAY; + + private BlockStatesData blockStates = new BlockStatesData(); + + private BiomesData biomes = new BiomesData(); + + } + + @Getter + @SuppressWarnings("FieldMayBeFinal") + public static class BlockStatesData { + + private BlockState[] palette = EMPTY_BLOCKSTATE_ARRAY; + + private long[] data = EMPTY_LONG_ARRAY; + + } + + @Getter + @SuppressWarnings("FieldMayBeFinal") + public static class BiomesData { + + private String[] palette = EMPTY_STRING_ARRAY; + + private long[] data = EMPTY_LONG_ARRAY; + + } + +} diff --git a/core/src/main/java/net/pl3x/map/core/world/CompressionType.java b/core/src/main/java/net/pl3x/map/core/world/CompressionType.java new file mode 100644 index 000000000..c8e3ec03e --- /dev/null +++ b/core/src/main/java/net/pl3x/map/core/world/CompressionType.java @@ -0,0 +1,41 @@ +package net.pl3x.map.core.world; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.zip.DeflaterOutputStream; +import java.util.zip.GZIPInputStream; +import java.util.zip.GZIPOutputStream; +import java.util.zip.InflaterInputStream; +import net.jpountz.lz4.LZ4BlockInputStream; +import net.jpountz.lz4.LZ4BlockOutputStream; + +public enum CompressionType { + + NONE(out -> out, in -> in), + GZIP(GZIPOutputStream::new, GZIPInputStream::new), + DEFLATE(DeflaterOutputStream::new, InflaterInputStream::new), + LZ4(LZ4BlockOutputStream::new, LZ4BlockInputStream::new); + + private final StreamTransformer compressor; + private final StreamTransformer decompressor; + + CompressionType(StreamTransformer compressor, StreamTransformer decompressor) { + this.compressor = compressor; + this.decompressor = decompressor; + } + + public OutputStream compress(OutputStream out) throws IOException { + return compressor.apply(out); + } + + public InputStream decompress(InputStream in) throws IOException { + return decompressor.apply(in); + } + + @FunctionalInterface + private interface StreamTransformer { + T apply(T original) throws IOException; + } + +} diff --git a/core/src/main/java/net/pl3x/map/core/world/EmptyChunk.java b/core/src/main/java/net/pl3x/map/core/world/EmptyChunk.java index d0bfc59ac..89b24cf7d 100644 --- a/core/src/main/java/net/pl3x/map/core/world/EmptyChunk.java +++ b/core/src/main/java/net/pl3x/map/core/world/EmptyChunk.java @@ -27,8 +27,9 @@ @NullMarked public class EmptyChunk extends Chunk { - protected EmptyChunk(World world, Region region) { - super(world, region); + + protected EmptyChunk(World world, Region region, int index) { + super(world, region, new Data(), index); } @Override @@ -51,6 +52,26 @@ public Biome getBiome(int x, int y, int z) { return Biome.DEFAULT; } + @Override + public int getMinY() { + return 0; + } + + @Override + public int getMaxY() { + return 0; + } + + @Override + public long getInhabitedTime() { + return 0; + } + + @Override + public boolean hasWorldSurfaceHeights() { + return false; + } + @Override public boolean noHeightmap() { return false; diff --git a/core/src/main/java/net/pl3x/map/core/world/Region.java b/core/src/main/java/net/pl3x/map/core/world/Region.java index 10084952b..0842f3cd2 100644 --- a/core/src/main/java/net/pl3x/map/core/world/Region.java +++ b/core/src/main/java/net/pl3x/map/core/world/Region.java @@ -23,25 +23,15 @@ */ package net.pl3x.map.core.world; -import java.io.BufferedInputStream; -import java.io.DataInputStream; import java.io.EOFException; import java.io.File; -import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; -import java.io.InputStream; import java.io.RandomAccessFile; import java.nio.file.Path; import java.util.Objects; -import net.jpountz.lz4.LZ4BlockInputStream; import net.pl3x.map.core.Pl3xMap; import net.pl3x.map.core.log.Logger; -import net.querz.mca.CompressionType; -import net.querz.nbt.io.NBTInputStream; -import net.querz.nbt.io.NamedTag; -import net.querz.nbt.tag.CompoundTag; -import net.querz.nbt.tag.Tag; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; @@ -52,6 +42,8 @@ public class Region { private final int regionZ; private final File regionFile; + private final ChunkLoader chunkLoader; + private final Chunk[] chunks = new Chunk[32 << 5]; private final int hash; @@ -62,6 +54,8 @@ public Region(World world, int regionX, int regionZ, Path regionFile) { this.regionZ = regionZ; this.regionFile = regionFile.toFile(); + this.chunkLoader = new ChunkLoader(world, this); + this.hash = Objects.hash(world, regionX, regionZ); } @@ -96,7 +90,7 @@ public Chunk getChunk(int chunkX, int chunkZ) { Logger.severe("Failed to load chunk at region [%d, %d]".formatted(chunkX, chunkZ), e); } if (chunk == null) { - return this.chunks[index] = new EmptyChunk(getWorld(), this); + return this.chunks[index] = new EmptyChunk(getWorld(), this, index); } } return chunk; @@ -117,36 +111,18 @@ public void loadChunks() throws IOException { public Chunk loadChunk(RandomAccessFile raf, int index) throws IOException { raf.seek(index * 4L); - int offset = raf.read() << 16; - offset |= (raf.read() & 0xFF) << 8; - offset |= raf.read() & 0xFF; - if (raf.readByte() == 0) { - return this.chunks[index] = new EmptyChunk(getWorld(), this); - } - raf.seek(4096L * offset + 4); // +4 skip chunk size - byte compressionTypeByte = raf.readByte(); - CompressionType compressionType = CompressionType.getFromID(compressionTypeByte); + byte[] header = new byte[4]; + raf.readFully(header, 0, 4); - // TODO: hotfix until querz' nbt library supports id as 3 for uncompressed - if (compressionType == null && compressionTypeByte == 3) { - compressionType = CompressionType.NONE; - } + long offset = (header[0] & 0xFF) << 16; + offset |= (header[1] & 0xFF) << 8; + offset |= header[2] & 0xFF; + offset *= 4096; + int size = (header[3] & 0xFF) * 4096; - if (compressionTypeByte != 4 && compressionType == null) { - throw new IOException("Invalid compression type " + compressionTypeByte); - } - - FileInputStream fileInputStream = new FileInputStream(raf.getFD()); - // TODO: hotfix until querz' nbt library supports the LZ4 compression type - InputStream decompress = compressionTypeByte == 4 ? new LZ4BlockInputStream(fileInputStream) : compressionType.decompress(fileInputStream); - DataInputStream dis = new DataInputStream(new BufferedInputStream(decompress)); - NamedTag tag = new NBTInputStream(dis).readTag(Tag.DEFAULT_MAX_DEPTH); - if (tag != null && tag.getTag() instanceof CompoundTag compoundTag) { - return this.chunks[index] = Chunk.create(getWorld(), this, compoundTag, index).populate(); - } else { - throw new IOException("Invalid data tag: " + (tag == null ? "null" : tag.getName())); - } + if (size <= 0) return this.chunks[index] = new EmptyChunk(world, this, index); + return this.chunks[index] = chunkLoader.load(raf, offset, index); } @Override diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index abae57819..6a7bf6fd1 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -31,6 +31,9 @@ gson="2.13.2" # https://github.com/google/gson guava="33.5.0-jre" # https://github.com/google/guava log4j="2.25.2" # https://github.com/apache/logging-log4j2 +lombok="1.18.42" # https://github.com/projectlombok/lombok + +bluenbt="3.5.0" # https://github.com/BlueMap-Minecraft/BlueNBT querzNbt="6.1" # https://github.com/Querz/NBT lz4Java="1.8.0" # https://github.com/lz4/lz4-java @@ -54,6 +57,8 @@ minecraft = { group = "com.mojang", name = "minecraft", version.ref = "minecraft log4j = { group = "org.apache.logging.log4j", name = "log4j-core", version.ref = "log4j" } +lombok = { module = "org.projectlombok:lombok", version.ref = "lombok" } + jspecifyAnnotations = { group = "org.jspecify", name = "jspecify", version = "1.0.0" } undertow = { group = "io.undertow", name = "undertow-core", version.ref = "undertow" } @@ -76,7 +81,7 @@ adventurePlatformFacet = { group = "net.kyori", name = "adventure-platform-facet adventurePlatformBukkit = { group = "net.kyori", name = "adventure-platform-bukkit", version.ref = "adventureBukkit" } adventurePlatformFabric = { group = "net.kyori", name = "adventure-platform-fabric", version.ref = "adventureFabric" } - +bluenbt = { module = "de.bluecolored:bluenbt", version.ref = "bluenbt" } caffeine = { group = "com.github.ben-manes.caffeine", name = "caffeine", version.ref = "caffeine" } querzNbt = { group = "com.github.Querz", name = "NBT", version.ref = "querzNbt" } lz4Java = { group = "org.lz4", name = "lz4-java", version.ref = "lz4Java" } From c4e402dcee71c3bb2c78f325254755cee7903e36 Mon Sep 17 00:00:00 2001 From: Lukas Rieger Date: Mon, 1 Jun 2026 00:27:52 +0200 Subject: [PATCH 2/5] Add trailing newline Co-authored-by: granny --- .../java/net/pl3x/map/core/world/BlockStateDeserializer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/net/pl3x/map/core/world/BlockStateDeserializer.java b/core/src/main/java/net/pl3x/map/core/world/BlockStateDeserializer.java index 94950c05b..a62d8b615 100644 --- a/core/src/main/java/net/pl3x/map/core/world/BlockStateDeserializer.java +++ b/core/src/main/java/net/pl3x/map/core/world/BlockStateDeserializer.java @@ -62,4 +62,4 @@ public BlockState read(NBTReader reader) throws IOException { return properties == null ? new BlockState(block) : new BlockState(block, properties); } -} \ No newline at end of file +} From 237c97c3abd65f22eecadf3f63ea5774e1c3d2b5 Mon Sep 17 00:00:00 2001 From: Lukas Rieger Date: Mon, 1 Jun 2026 00:33:11 +0200 Subject: [PATCH 3/5] Remove lombok --- core/build.gradle.kts | 4 ---- .../main/java/net/pl3x/map/core/world/Chunk.java | 6 ++++-- .../net/pl3x/map/core/world/ChunkLoader.java | 16 +++++++++++----- .../java/net/pl3x/map/core/world/Chunk_1_13.java | 9 ++------- .../java/net/pl3x/map/core/world/Chunk_1_16.java | 9 ++------- .../java/net/pl3x/map/core/world/Chunk_1_18.java | 8 +------- 6 files changed, 20 insertions(+), 32 deletions(-) diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 235e74152..3b9a8e0ff 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -58,10 +58,6 @@ dependencies { // provided by mojang compileOnly(libs.gson) compileOnly(libs.guava) - - // lombok - compileOnly(libs.lombok) - annotationProcessor(libs.lombok) } tasks { diff --git a/core/src/main/java/net/pl3x/map/core/world/Chunk.java b/core/src/main/java/net/pl3x/map/core/world/Chunk.java index 0cc31005a..2771f28de 100644 --- a/core/src/main/java/net/pl3x/map/core/world/Chunk.java +++ b/core/src/main/java/net/pl3x/map/core/world/Chunk.java @@ -27,7 +27,6 @@ import java.util.LinkedList; import java.util.List; import java.util.Objects; -import lombok.Getter; import net.pl3x.map.core.util.Colors; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; @@ -282,12 +281,15 @@ public List getGlassColors() { } @SuppressWarnings("FieldMayBeFinal") - @Getter public static class Data { @NBTName("DataVersion") private int dataVersion = 0; + public int getDataVersion() { + return dataVersion; + } + } } diff --git a/core/src/main/java/net/pl3x/map/core/world/ChunkLoader.java b/core/src/main/java/net/pl3x/map/core/world/ChunkLoader.java index 3e0017c1b..5758318cb 100644 --- a/core/src/main/java/net/pl3x/map/core/world/ChunkLoader.java +++ b/core/src/main/java/net/pl3x/map/core/world/ChunkLoader.java @@ -33,11 +33,8 @@ import java.io.InputStream; import java.io.RandomAccessFile; import java.util.List; -import lombok.Getter; -import lombok.RequiredArgsConstructor; import org.jspecify.annotations.Nullable; -@RequiredArgsConstructor public class ChunkLoader { public static final @Nullable CompressionType[] CHUNK_COMPRESSION_MAP = new CompressionType[255]; @@ -58,6 +55,11 @@ public class ChunkLoader { private final World world; private final Region region; + public ChunkLoader(World world, Region region) { + this.world = world; + this.region = region; + } + // sorted list of chunk-versions, loaders at the start of the list are preferred over loaders at the end private static final List> CHUNK_VERSION_LOADERS = List.of( new ChunkVersionLoader<>(Chunk_1_18.Data.class, Chunk_1_18::new, 2844), @@ -101,14 +103,18 @@ public Chunk load(RandomAccessFile raf, long offset, int index) throws IOExcepti return null; } - @RequiredArgsConstructor - @Getter private static class ChunkVersionLoader { private final Class dataType; private final ChunkConstructor constructor; private final int dataVersion; + ChunkVersionLoader(Class dataType, ChunkConstructor constructor, int dataVersion) { + this.dataType = dataType; + this.constructor = constructor; + this.dataVersion = dataVersion; + } + public Chunk load(World world, Region region, InputStream in, int index) throws IOException { try { D data = BLUENBT.read(in, dataType); diff --git a/core/src/main/java/net/pl3x/map/core/world/Chunk_1_13.java b/core/src/main/java/net/pl3x/map/core/world/Chunk_1_13.java index 5b217631c..1798a5774 100644 --- a/core/src/main/java/net/pl3x/map/core/world/Chunk_1_13.java +++ b/core/src/main/java/net/pl3x/map/core/world/Chunk_1_13.java @@ -25,7 +25,6 @@ package net.pl3x.map.core.world; import de.bluecolored.bluenbt.NBTName; -import lombok.Getter; import net.pl3x.map.core.util.MCAMath; import org.jetbrains.annotations.Nullable; @@ -63,7 +62,7 @@ public Chunk_1_13(World world, Region region, Data data, int index) { // find section min/max y for (SectionData sectionData : sectionsData) { - int y = sectionData.getY(); + int y = sectionData.y; if (min > y) min = y; if (max < y) max = y; } @@ -165,7 +164,7 @@ public Section(SectionData sectionData) { this.blockPalette = sectionData.palette; this.blocks = sectionData.blockStates; - this.blockLight = sectionData.getBlockLight(); + this.blockLight = sectionData.blockLight; this.bitsPerBlock = this.blocks.length >> 6; // available longs * 64 (bits per long) / 4096 (blocks per section) (floored result) } @@ -202,7 +201,6 @@ public int getSectionY() { } - @Getter @SuppressWarnings("FieldMayBeFinal") public static class Data extends Chunk.Data { @@ -211,7 +209,6 @@ public static class Data extends Chunk.Data { } - @Getter @SuppressWarnings("FieldMayBeFinal") public static class Level { @@ -232,7 +229,6 @@ public static class Level { } - @Getter @SuppressWarnings("FieldMayBeFinal") public static class HeightmapsData { @@ -241,7 +237,6 @@ public static class HeightmapsData { } - @Getter @SuppressWarnings("FieldMayBeFinal") public static class SectionData { diff --git a/core/src/main/java/net/pl3x/map/core/world/Chunk_1_16.java b/core/src/main/java/net/pl3x/map/core/world/Chunk_1_16.java index 33ce02332..2ab80e6dd 100644 --- a/core/src/main/java/net/pl3x/map/core/world/Chunk_1_16.java +++ b/core/src/main/java/net/pl3x/map/core/world/Chunk_1_16.java @@ -25,7 +25,6 @@ package net.pl3x.map.core.world; import de.bluecolored.bluenbt.NBTName; -import lombok.Getter; import net.pl3x.map.core.util.MCAMath; import net.pl3x.map.core.util.PackedIntArrayAccess; import org.jetbrains.annotations.Nullable; @@ -70,7 +69,7 @@ public Chunk_1_16(World world, Region region, Data data, int index) { // find section min/max y for (SectionData sectionData : sectionsData) { - int y = sectionData.getY(); + int y = sectionData.y; if (min > y) min = y; if (max < y) max = y; } @@ -172,7 +171,7 @@ public Section(SectionData sectionData) { this.blockPalette = sectionData.palette; this.blocks = new PackedIntArrayAccess(sectionData.blockStates, BLOCKS_PER_SECTION); - this.blockLight = sectionData.getBlockLight(); + this.blockLight = sectionData.blockLight; } public BlockState getBlockState(int x, int y, int z) { @@ -203,7 +202,6 @@ public int getSectionY() { } - @Getter @SuppressWarnings("FieldMayBeFinal") public static class Data extends Chunk.Data { @@ -212,7 +210,6 @@ public static class Data extends Chunk.Data { } - @Getter @SuppressWarnings("FieldMayBeFinal") public static class Level { @@ -233,7 +230,6 @@ public static class Level { } - @Getter @SuppressWarnings("FieldMayBeFinal") public static class HeightmapsData { @@ -242,7 +238,6 @@ public static class HeightmapsData { } - @Getter @SuppressWarnings("FieldMayBeFinal") public static class SectionData { diff --git a/core/src/main/java/net/pl3x/map/core/world/Chunk_1_18.java b/core/src/main/java/net/pl3x/map/core/world/Chunk_1_18.java index 1d09570c3..113b58959 100644 --- a/core/src/main/java/net/pl3x/map/core/world/Chunk_1_18.java +++ b/core/src/main/java/net/pl3x/map/core/world/Chunk_1_18.java @@ -25,7 +25,6 @@ package net.pl3x.map.core.world; import de.bluecolored.bluenbt.NBTName; -import lombok.Getter; import net.pl3x.map.core.util.MCAMath; import net.pl3x.map.core.util.PackedIntArrayAccess; import org.jetbrains.annotations.Nullable; @@ -67,7 +66,7 @@ public Chunk_1_18(World world, Region region, Data data, int index) { // find section min/max y for (SectionData sectionData : sectionsData) { - int y = sectionData.getY(); + int y = sectionData.y; if (min > y) min = y; if (max < y) max = y; } @@ -216,7 +215,6 @@ public int getSectionY() { } - @Getter @SuppressWarnings("FieldMayBeFinal") public static class Data extends Chunk.Data { @@ -233,7 +231,6 @@ public static class Data extends Chunk.Data { } - @Getter @SuppressWarnings("FieldMayBeFinal") public static class HeightmapsData { @@ -242,7 +239,6 @@ public static class HeightmapsData { } - @Getter @SuppressWarnings("FieldMayBeFinal") public static class SectionData { @@ -258,7 +254,6 @@ public static class SectionData { } - @Getter @SuppressWarnings("FieldMayBeFinal") public static class BlockStatesData { @@ -268,7 +263,6 @@ public static class BlockStatesData { } - @Getter @SuppressWarnings("FieldMayBeFinal") public static class BiomesData { From 8e7fa9278a25a7e0de915e912364a1c8b0a07199 Mon Sep 17 00:00:00 2001 From: Lukas Rieger Date: Mon, 1 Jun 2026 09:39:56 +0200 Subject: [PATCH 4/5] Fix map not rendering :> --- core/src/main/java/net/pl3x/map/core/world/ChunkLoader.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/net/pl3x/map/core/world/ChunkLoader.java b/core/src/main/java/net/pl3x/map/core/world/ChunkLoader.java index 5758318cb..9d9a78912 100644 --- a/core/src/main/java/net/pl3x/map/core/world/ChunkLoader.java +++ b/core/src/main/java/net/pl3x/map/core/world/ChunkLoader.java @@ -93,7 +93,7 @@ public Chunk load(RandomAccessFile raf, long offset, int index) throws IOExcepti lastUsedLoader = actualLoader; } - return chunk.isFull() ? chunk : new EmptyChunk(world, region, index); + return chunk.isFull() ? chunk.populate() : new EmptyChunk(world, region, index); } private @Nullable ChunkVersionLoader findBestLoaderForVersion(int version) { From b9b088e804904375ffd3c63f97b0d60f4101b4ce Mon Sep 17 00:00:00 2001 From: Lukas Rieger Date: Thu, 4 Jun 2026 15:35:52 +0200 Subject: [PATCH 5/5] remove unused library versions --- gradle/libs.versions.toml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 6a7bf6fd1..be63daa42 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -31,10 +31,7 @@ gson="2.13.2" # https://github.com/google/gson guava="33.5.0-jre" # https://github.com/google/guava log4j="2.25.2" # https://github.com/apache/logging-log4j2 -lombok="1.18.42" # https://github.com/projectlombok/lombok - bluenbt="3.5.0" # https://github.com/BlueMap-Minecraft/BlueNBT -querzNbt="6.1" # https://github.com/Querz/NBT lz4Java="1.8.0" # https://github.com/lz4/lz4-java simpleYaml="1.8.4" # https://github.com/Carleslc/Simple-YAML @@ -57,8 +54,6 @@ minecraft = { group = "com.mojang", name = "minecraft", version.ref = "minecraft log4j = { group = "org.apache.logging.log4j", name = "log4j-core", version.ref = "log4j" } -lombok = { module = "org.projectlombok:lombok", version.ref = "lombok" } - jspecifyAnnotations = { group = "org.jspecify", name = "jspecify", version = "1.0.0" } undertow = { group = "io.undertow", name = "undertow-core", version.ref = "undertow" } @@ -83,7 +78,6 @@ adventurePlatformFabric = { group = "net.kyori", name = "adventure-platform-fabr bluenbt = { module = "de.bluecolored:bluenbt", version.ref = "bluenbt" } caffeine = { group = "com.github.ben-manes.caffeine", name = "caffeine", version.ref = "caffeine" } -querzNbt = { group = "com.github.Querz", name = "NBT", version.ref = "querzNbt" } lz4Java = { group = "org.lz4", name = "lz4-java", version.ref = "lz4Java" } simpleYaml = { group = "com.github.Carleslc.Simple-YAML", name = "Simple-Yaml", version.ref = "simpleYaml" }