From 5390a33fa3dd1c1d2b8652d325a1a9da01497a5c Mon Sep 17 00:00:00 2001 From: "Lukas Rieger (Blue)" Date: Tue, 5 Sep 2023 22:01:03 +0200 Subject: [PATCH] First working render with BlueNBT --- BlueMapCore/build.gradle.kts | 8 +- .../bluemap/core/mca/ChunkAnvil113.java | 18 +- .../bluemap/core/mca/ChunkAnvil115.java | 17 +- .../bluemap/core/mca/ChunkAnvil116.java | 18 +- .../bluemap/core/mca/ChunkAnvil118.java | 184 ++++++------------ .../bluemap/core/mca/MCAChunk.java | 21 +- .../bluecolored/bluemap/core/mca/MCAMath.java | 19 +- .../bluemap/core/mca/MCAWorld.java | 30 ++- .../core/mca/PackedIntArrayAccess.java | 12 +- .../bluemap/core/mca/data/BiomesData.java | 14 ++ .../core/mca/data/BlockStatesData.java | 15 ++ .../bluemap/core/mca/data/ChunkData.java | 17 ++ .../bluemap/core/mca/data/HeightmapsData.java | 17 ++ .../bluemap/core/mca/data/LevelData.java | 19 ++ .../bluemap/core/mca/data/SectionData.java | 20 ++ .../deserializer/BlockStateDeserializer.java | 44 +++++ .../bluemap/core/mca/region/LinearRegion.java | 14 +- .../bluemap/core/mca/region/MCARegion.java | 34 ++-- .../core/mca/resourcepack/Dimension.java | 16 ++ .../core/mca/resourcepack/DimensionType.java | 65 +++++++ .../bluemap/core/storage/Compression.java | 7 +- 21 files changed, 379 insertions(+), 230 deletions(-) create mode 100644 BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/data/BiomesData.java create mode 100644 BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/data/BlockStatesData.java create mode 100644 BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/data/ChunkData.java create mode 100644 BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/data/HeightmapsData.java create mode 100644 BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/data/LevelData.java create mode 100644 BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/data/SectionData.java create mode 100644 BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/deserializer/BlockStateDeserializer.java create mode 100644 BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/resourcepack/Dimension.java create mode 100644 BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/resourcepack/DimensionType.java diff --git a/BlueMapCore/build.gradle.kts b/BlueMapCore/build.gradle.kts index 7f1a16d2..366e0695 100644 --- a/BlueMapCore/build.gradle.kts +++ b/BlueMapCore/build.gradle.kts @@ -61,16 +61,22 @@ dependencies { api ("commons-io:commons-io:2.5") api ("org.spongepowered:configurate-hocon:4.1.2") api ("org.spongepowered:configurate-gson:4.1.2") - api ("com.github.Querz:NBT:4.0") + //api ("com.github.Querz:NBT:4.0") + api ("com.github.BlueMap-Minecraft:BlueNBT:v1.2.0") api ("org.apache.commons:commons-dbcp2:2.9.0") api ("io.airlift:aircompressor:0.24") api ("de.bluecolored.bluemap.api:BlueMapAPI") compileOnly ("org.jetbrains:annotations:23.0.0") + compileOnly ("org.projectlombok:lombok:1.18.28") + + annotationProcessor ("org.projectlombok:lombok:1.18.28") testImplementation ("org.junit.jupiter:junit-jupiter:5.8.2") testRuntimeOnly ("org.junit.jupiter:junit-jupiter-engine:5.8.2") + testCompileOnly ("org.projectlombok:lombok:1.18.28") + testAnnotationProcessor ("org.projectlombok:lombok:1.18.28") } spotless { diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/ChunkAnvil113.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/ChunkAnvil113.java index 35752b08..b0541a48 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/ChunkAnvil113.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/ChunkAnvil113.java @@ -24,21 +24,12 @@ */ package de.bluecolored.bluemap.core.mca; -import de.bluecolored.bluemap.core.logger.Logger; -import de.bluecolored.bluemap.core.world.Biome; -import de.bluecolored.bluemap.core.world.BlockState; -import de.bluecolored.bluemap.core.world.LightData; -import net.querz.nbt.*; - -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; -import java.util.Map.Entry; - @SuppressWarnings("FieldMayBeFinal") -public class ChunkAnvil113 extends MCAChunk { +public class ChunkAnvil113 /* extends MCAChunk */ { private static final long[] EMPTY_LONG_ARRAY = new long[0]; + /* + private boolean isGenerated; private boolean hasLight; private long inhabitedTime; @@ -48,6 +39,7 @@ public class ChunkAnvil113 extends MCAChunk { private long[] oceanFloorHeights = EMPTY_LONG_ARRAY; private long[] worldSurfaceHeights = EMPTY_LONG_ARRAY; + @SuppressWarnings("unchecked") public ChunkAnvil113(MCAWorld world, CompoundTag chunkTag) { super(world, chunkTag); @@ -255,4 +247,6 @@ public LightData getLightData(int x, int y, int z, LightData target) { } } + */ + } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/ChunkAnvil115.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/ChunkAnvil115.java index e13cb685..6024d659 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/ChunkAnvil115.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/ChunkAnvil115.java @@ -24,21 +24,12 @@ */ package de.bluecolored.bluemap.core.mca; -import de.bluecolored.bluemap.core.logger.Logger; -import de.bluecolored.bluemap.core.world.Biome; -import de.bluecolored.bluemap.core.world.BlockState; -import de.bluecolored.bluemap.core.world.LightData; -import net.querz.nbt.*; - -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; -import java.util.Map.Entry; - @SuppressWarnings("FieldMayBeFinal") -public class ChunkAnvil115 extends MCAChunk { +public class ChunkAnvil115 /* extends MCAChunk */ { private static final long[] EMPTY_LONG_ARRAY = new long[0]; + /* + private boolean isGenerated; private boolean hasLight; private long inhabitedTime; @@ -261,4 +252,6 @@ public LightData getLightData(int x, int y, int z, LightData target) { } } + */ + } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/ChunkAnvil116.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/ChunkAnvil116.java index aaf4c429..1c60ac84 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/ChunkAnvil116.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/ChunkAnvil116.java @@ -24,22 +24,12 @@ */ package de.bluecolored.bluemap.core.mca; -import de.bluecolored.bluemap.core.logger.Logger; -import de.bluecolored.bluemap.core.world.Biome; -import de.bluecolored.bluemap.core.world.BlockState; -import de.bluecolored.bluemap.core.world.LightData; -import net.querz.nbt.*; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; -import java.util.Map.Entry; - @SuppressWarnings("FieldMayBeFinal") -public class ChunkAnvil116 extends MCAChunk { +public class ChunkAnvil116 /* extends MCAChunk */ { private static final long[] EMPTY_LONG_ARRAY = new long[0]; + /* + private boolean isGenerated; private boolean hasLight; @@ -290,4 +280,6 @@ public LightData getLightData(int x, int y, int z, LightData target) { } } + */ + } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/ChunkAnvil118.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/ChunkAnvil118.java index a790f9de..a41c1f42 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/ChunkAnvil118.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/ChunkAnvil118.java @@ -25,75 +25,70 @@ package de.bluecolored.bluemap.core.mca; import de.bluecolored.bluemap.core.logger.Logger; +import de.bluecolored.bluemap.core.mca.data.*; import de.bluecolored.bluemap.core.world.Biome; import de.bluecolored.bluemap.core.world.BlockState; import de.bluecolored.bluemap.core.world.LightData; -import net.querz.nbt.*; -import java.util.*; -import java.util.Map.Entry; - -@SuppressWarnings("FieldMayBeFinal") public class ChunkAnvil118 extends MCAChunk { - private static final long[] EMPTY_LONG_ARRAY = new long[0]; - private static final BlockState[] EMPTY_BLOCK_STATE_ARRAY = new BlockState[0]; - private static final String[] EMPTY_STRING_ARRAY = new String[0]; - private boolean isGenerated; - private boolean hasLight; + private final boolean isGenerated; + private final boolean hasLight; - private long inhabitedTime; + private final long inhabitedTime; - private int sectionMin, sectionMax; - private Section[] sections; + private final int sectionMin, sectionMax; + private final Section[] sections; - private long[] oceanFloorHeights = EMPTY_LONG_ARRAY; - private long[] worldSurfaceHeights = EMPTY_LONG_ARRAY; + private final long[] oceanFloorHeights; + private final long[] worldSurfaceHeights; - @SuppressWarnings("unchecked") - public ChunkAnvil118(MCAWorld world, CompoundTag chunkTag) { - super(world, chunkTag); + public ChunkAnvil118(MCAWorld world, ChunkData chunkData) { + super(world, chunkData); - String status = chunkTag.getString("Status"); - this.isGenerated = status.equals("full") || status.equals("minecraft:full"); - this.hasLight = isGenerated; + String status = chunkData.getStatus(); + boolean generated = status.equals("minecraft:full") || status.equals("full"); + this.hasLight = generated; + if (!generated && getWorld().isIgnoreMissingLightData()) + generated = !status.equals("empty") && !status.equals("minecraft:empty"); + this.isGenerated = generated; - this.inhabitedTime = chunkTag.getLong("InhabitedTime"); + this.inhabitedTime = chunkData.getInhabitedTime(); - if (!isGenerated && getWorld().isIgnoreMissingLightData()) { - isGenerated = !status.equals("empty") && !status.equals("minecraft:empty"); - } + HeightmapsData heightmapsData = chunkData.getHeightmaps(); + this.worldSurfaceHeights = heightmapsData.getWorldSurface(); + this.oceanFloorHeights = heightmapsData.getOceanFloor(); - if (chunkTag.containsKey("Heightmaps")) { - CompoundTag heightmapsTag = chunkTag.getCompoundTag("Heightmaps"); - this.worldSurfaceHeights = heightmapsTag.getLongArray("WORLD_SURFACE"); - this.oceanFloorHeights = heightmapsTag.getLongArray("OCEAN_FLOOR"); - } + SectionData[] sectionDatas = chunkData.getSections(); + if (sectionDatas != null && sectionDatas.length > 0) { + int min = Integer.MAX_VALUE; + int max = Integer.MIN_VALUE; - if (chunkTag.containsKey("sections")) { - this.sectionMin = Integer.MAX_VALUE; - this.sectionMax = Integer.MIN_VALUE; + // find section min/max y + for (SectionData sectionData : sectionDatas) { + int y = sectionData.getY(); + if (min > y) min = y; + if (max < y) max = y; + } - ListTag sectionsTag = (ListTag) chunkTag.getListTag("sections"); - ArrayList
sectionList = new ArrayList<>(sectionsTag.size()); - - for (CompoundTag sectionTag : sectionsTag) { - - Section section = new Section(sectionTag); + // load sections into ordered array + this.sections = new Section[1 + max - min]; + for (SectionData sectionData : sectionDatas) { + Section section = new Section(sectionData); int y = section.getSectionY(); - if (sectionMin > y) sectionMin = y; - if (sectionMax < y) sectionMax = y; + if (min > y) min = y; + if (max < y) max = y; - sectionList.add(section); + sections[section.sectionY - min] = section; } - sections = new Section[1 + sectionMax - sectionMin]; - for (Section section : sectionList) { - sections[section.sectionY - sectionMin] = section; - } + this.sectionMin = min; + this.sectionMax = max; } else { - sections = new Section[0]; + this.sections = new Section[0]; + this.sectionMin = 0; + this.sectionMax = 0; } } @@ -172,77 +167,33 @@ private Section getSection(int y) { } private static class Section { - private int sectionY; - private byte[] blockLight; - private byte[] skyLight; - private long[] blocks = EMPTY_LONG_ARRAY; - private long[] biomes = EMPTY_LONG_ARRAY; - private BlockState[] blockPalette = EMPTY_BLOCK_STATE_ARRAY; - private String[] biomePalette = EMPTY_STRING_ARRAY; + private final int sectionY; + private final byte[] blockLight; + private final byte[] skyLight; + private final long[] blocks; + private final long[] biomes; + private final BlockState[] blockPalette; + private final String[] biomePalette; - private int bitsPerBlock, bitsPerBiome; + private final int bitsPerBlock, bitsPerBiome; - @SuppressWarnings("unchecked") - public Section(CompoundTag sectionData) { - this.sectionY = sectionData.get("Y", NumberTag.class).asInt(); - this.blockLight = sectionData.getByteArray("BlockLight"); - this.skyLight = sectionData.getByteArray("SkyLight"); + public Section(SectionData sectionData) { + this.sectionY = sectionData.getY(); + this.blockLight = sectionData.getBlockLight(); + this.skyLight = sectionData.getSkyLight(); - // blocks - CompoundTag blockStatesTag = sectionData.getCompoundTag("block_states"); - if (blockStatesTag != null) { - // block data - this.blocks = blockStatesTag.getLongArray("data"); + BlockStatesData blockStates = sectionData.getBlockStates(); + this.blocks = blockStates.getData(); + this.blockPalette = blockStates.getPalette(); - // block palette - ListTag paletteTag = (ListTag) blockStatesTag.getListTag("palette"); - if (paletteTag != null) { - this.blockPalette = new BlockState[paletteTag.size()]; - for (int i = 0; i < this.blockPalette.length; i++) { - blockPalette[i] = readBlockStatePaletteEntry(paletteTag.get(i)); - } - } - } - - // biomes - CompoundTag biomesTag = sectionData.getCompoundTag("biomes"); - if (biomesTag != null) { - // biomes data - this.biomes = biomesTag.getLongArray("data"); - - // biomes palette - ListTag paletteTag = (ListTag) biomesTag.getListTag("palette"); - if (paletteTag != null) { - this.biomePalette = new String[paletteTag.size()]; - for (int i = 0; i < this.biomePalette.length; i++) { - biomePalette[i] = paletteTag.get(i).getValue(); - } - } - } - - if (blocks.length < 256 && blocks.length > 0) blocks = Arrays.copyOf(blocks, 256); - if (blockLight.length < 2048 && blockLight.length > 0) blockLight = Arrays.copyOf(blockLight, 2048); - if (skyLight.length < 2048 && skyLight.length > 0) skyLight = Arrays.copyOf(skyLight, 2048); + BiomesData biomesData = sectionData.getBiomes(); + this.biomes = biomesData.getData(); + this.biomePalette = biomesData.getPalette(); this.bitsPerBlock = this.blocks.length >> 6; // available longs * 64 (bits per long) / 4096 (blocks per section) (floored result) this.bitsPerBiome = MCAMath.ceilLog2(this.biomePalette.length); } - private BlockState readBlockStatePaletteEntry(CompoundTag paletteEntry) { - String id = paletteEntry.getString("Name"); - if (BlockState.AIR.getFormatted().equals(id)) return BlockState.AIR; //shortcut to save time and memory - - Map properties = new LinkedHashMap<>(); - if (paletteEntry.containsKey("Properties")) { - CompoundTag propertiesTag = paletteEntry.getCompoundTag("Properties"); - for (Entry> property : propertiesTag) { - properties.put(property.getKey().toLowerCase(), ((StringTag) property.getValue()).getValue().toLowerCase()); - } - } - - return new BlockState(id, properties); - } - public int getSectionY() { return sectionY; } @@ -251,10 +202,7 @@ public BlockState getBlockState(int x, int y, int z) { if (blockPalette.length == 1) return blockPalette[0]; if (blocks.length == 0) return BlockState.AIR; - x &= 0xF; y &= 0xF; z &= 0xF; // Math.floorMod(pos.getX(), 16) - - int blockIndex = y * 256 + z * 16 + x; - + int blockIndex = (y & 0xF) << 8 | (z & 0xF) << 4 | x & 0xF; long value = MCAMath.getValueFromLongArray(blocks, blockIndex, bitsPerBlock); if (value >= blockPalette.length) { Logger.global.noFloodWarning("palettewarning", "Got block-palette value " + value + " but palette has size of " + blockPalette.length + "! (Future occasions of this error will not be logged)"); @@ -267,9 +215,7 @@ public BlockState getBlockState(int x, int y, int z) { public LightData getLightData(int x, int y, int z, LightData target) { if (blockLight.length == 0 && skyLight.length == 0) return target.set(0, 0); - x &= 0xF; y &= 0xF; z &= 0xF; // Math.floorMod(pos.getX(), 16) - - int blockByteIndex = y * 256 + z * 16 + x; + int blockByteIndex = (y & 0xF) << 8 | (z & 0xF) << 4 | x & 0xF; int blockHalfByteIndex = blockByteIndex >> 1; // blockByteIndex / 2 boolean largeHalf = (blockByteIndex & 0x1) != 0; // (blockByteIndex % 2) == 0 @@ -283,11 +229,7 @@ public String getBiome(int x, int y, int z) { if (biomePalette.length == 0) return Biome.DEFAULT.getValue(); if (biomePalette.length == 1 || biomes.length == 0) return biomePalette[0]; - x = (x & 0xF) / 4; // Math.floorMod(pos.getX(), 16) / 4 - z = (z & 0xF) / 4; - y = (y & 0xF) / 4; - int biomeIndex = y * 16 + z * 4 + x; - + int biomeIndex = (y & 0b1100) << 2 | z & 0b1100 | (x & 0b1100) >> 2; long value = MCAMath.getValueFromLongArray(biomes, biomeIndex, bitsPerBiome); if (value >= biomePalette.length) { Logger.global.noFloodWarning("biomepalettewarning", "Got biome-palette value " + value + " but palette has size of " + biomePalette.length + "! (Future occasions of this error will not be logged)"); diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/MCAChunk.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/MCAChunk.java index b85e3276..c34598de 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/MCAChunk.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/MCAChunk.java @@ -24,10 +24,10 @@ */ package de.bluecolored.bluemap.core.mca; +import de.bluecolored.bluemap.core.mca.data.ChunkData; import de.bluecolored.bluemap.core.world.BlockState; import de.bluecolored.bluemap.core.world.Chunk; import de.bluecolored.bluemap.core.world.LightData; -import net.querz.nbt.CompoundTag; import java.io.IOException; @@ -46,9 +46,9 @@ protected MCAChunk(MCAWorld world) { this.dataVersion = -1; } - protected MCAChunk(MCAWorld world, CompoundTag chunkTag) { + protected MCAChunk(MCAWorld world, ChunkData chunkData) { this.world = world; - dataVersion = chunkTag.getInt("DataVersion"); + dataVersion = chunkData.getDataVersion(); } @Override @@ -90,13 +90,16 @@ protected MCAWorld getWorld() { return world; } - public static MCAChunk create(MCAWorld world, CompoundTag chunkTag) throws IOException { - int version = chunkTag.getInt("DataVersion"); + public static MCAChunk create(MCAWorld world, ChunkData chunkData) throws IOException { + int version = chunkData.getDataVersion(); - if (version < 2200) return new ChunkAnvil113(world, chunkTag); - if (version < 2500) return new ChunkAnvil115(world, chunkTag); - if (version < 2844) return new ChunkAnvil116(world, chunkTag); - return new ChunkAnvil118(world, chunkTag); + /* + if (version < 2200) return new ChunkAnvil113(world, chunkData); + if (version < 2500) return new ChunkAnvil115(world, chunkData); + if (version < 2844) return new ChunkAnvil116(world, chunkData); + */ + + return new ChunkAnvil118(world, chunkData); } @Override diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/MCAMath.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/MCAMath.java index ed15d883..b6726d1d 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/MCAMath.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/MCAMath.java @@ -24,8 +24,18 @@ */ package de.bluecolored.bluemap.core.mca; +import com.google.gson.reflect.TypeToken; +import de.bluecolored.bluemap.core.mca.deserializer.BlockStateDeserializer; +import de.bluecolored.bluemap.core.world.BlockState; +import de.bluecolored.bluenbt.BlueNBT; + public class MCAMath { + public static final BlueNBT BLUENBT = new BlueNBT(); + static { + BLUENBT.register(TypeToken.get(BlockState.class), new BlockStateDeserializer()); + } + /** * Having a long array where each long contains as many values as fit in it without overflowing, returning the "valueIndex"-th value when each value has "bitsPerValue" bits. */ @@ -34,6 +44,7 @@ public static long getValueFromLongArray(long[] data, int valueIndex, int bitsPe int longIndex = valueIndex / valuesPerLong; int bitIndex = (valueIndex % valuesPerLong) * bitsPerValue; + if (longIndex >= data.length) return 0; long value = data[longIndex] >>> bitIndex; return value & (0xFFFFFFFFFFFFFFFFL >>> -bitsPerValue); @@ -63,12 +74,8 @@ public static long getValueFromLongStream(long[] data, int valueIndex, int bitsP * The value is treated as an unsigned byte. */ public static int getByteHalf(int value, boolean largeHalf) { - value = value & 0xFF; - if (largeHalf) { - value = value >> 4; - } - value = value & 0xF; - return value; + if (largeHalf) return value >> 4 & 0xF; + return value & 0xF; } public static int ceilLog2(int n) { diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/MCAWorld.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/MCAWorld.java index 0d681ced..143966f9 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/MCAWorld.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/MCAWorld.java @@ -31,15 +31,12 @@ import de.bluecolored.bluemap.api.debug.DebugDump; import de.bluecolored.bluemap.core.BlueMap; import de.bluecolored.bluemap.core.logger.Logger; +import de.bluecolored.bluemap.core.mca.data.LevelData; import de.bluecolored.bluemap.core.mca.region.RegionType; import de.bluecolored.bluemap.core.util.Vector2iCache; import de.bluecolored.bluemap.core.world.*; -import net.querz.nbt.CompoundTag; -import net.querz.nbt.NBTUtil; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; +import java.io.*; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; @@ -47,6 +44,7 @@ import java.util.Collections; import java.util.List; import java.util.concurrent.TimeUnit; +import java.util.zip.GZIPInputStream; @DebugDump public class MCAWorld implements World { @@ -84,20 +82,18 @@ public MCAWorld(Path worldFolder, int skyLight, boolean ignoreMissingLightData) .expireAfterWrite(1, TimeUnit.MINUTES) .build(this::loadChunk); - try { - Path levelFile = resolveLevelFile(worldFolder); - CompoundTag level = (CompoundTag) NBTUtil.readTag(levelFile.toFile()); - CompoundTag levelData = level.getCompoundTag("Data"); - - this.name = levelData.getString("LevelName"); - + Path levelFile = resolveLevelFile(worldFolder); + try (InputStream in = new GZIPInputStream(new BufferedInputStream(Files.newInputStream(levelFile)))) { + LevelData level = MCAMath.BLUENBT.read(in, LevelData.class); + LevelData.Data levelData = level.getData(); + this.name = levelData.getLevelName(); this.spawnPoint = new Vector3i( - levelData.getInt("SpawnX"), - levelData.getInt("SpawnY"), - levelData.getInt("SpawnZ") + levelData.getSpawnX(), + levelData.getSpawnY(), + levelData.getSpawnZ() ); - } catch (ClassCastException | NullPointerException ex) { - throw new IOException("Invalid level.dat format!", ex); + } catch (IOException ex) { + throw new IOException("Failed to read level.dat!", ex); } } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/PackedIntArrayAccess.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/PackedIntArrayAccess.java index 879d01ce..be85809a 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/PackedIntArrayAccess.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/PackedIntArrayAccess.java @@ -72,7 +72,8 @@ public class PackedIntArrayAccess { private final long[] data; private final long maxValue; - private final int elementsPerLong, indexScale, indexOffset, indexShift; + private final int elementsPerLong, indexShift; + private final long indexScale, indexOffset; public PackedIntArrayAccess(int bitsPerElement, long[] data) { this.bitsPerElement = bitsPerElement; @@ -82,22 +83,21 @@ public PackedIntArrayAccess(int bitsPerElement, long[] data) { this.elementsPerLong = (char)(64 / this.bitsPerElement); int i = 3 * (this.elementsPerLong - 1); - this.indexScale = INDEX_PARAMETERS[i]; - this.indexOffset = INDEX_PARAMETERS[i + 1]; + this.indexScale = Integer.toUnsignedLong(INDEX_PARAMETERS[i]); + this.indexOffset = Integer.toUnsignedLong(INDEX_PARAMETERS[i + 1]); this.indexShift = INDEX_PARAMETERS[i + 2]; } public int get(int i) { int j = this.storageIndex(i); + if (j >= this.data.length) return 0; long l = this.data[j]; int k = (i - j * this.elementsPerLong) * this.bitsPerElement; return (int)(l >> k & this.maxValue); } public int storageIndex(int i) { - long l = Integer.toUnsignedLong(this.indexScale); - long m = Integer.toUnsignedLong(this.indexOffset); - return (int) ((long) i * l + m >> 32 >> this.indexShift); + return (int) ((long) i * this.indexScale + this.indexOffset >> 32 >> this.indexShift); } } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/data/BiomesData.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/data/BiomesData.java new file mode 100644 index 00000000..654dd0a3 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/data/BiomesData.java @@ -0,0 +1,14 @@ +package de.bluecolored.bluemap.core.mca.data; + +import lombok.Getter; + +@Getter +@SuppressWarnings("FieldMayBeFinal") +public class BiomesData { + private static final String[] EMPTY_STRING_ARRAY = new String[0]; + private static final long[] EMPTY_LONG_ARRAY = new long[0]; + + private String[] palette = EMPTY_STRING_ARRAY; + private long[] data = EMPTY_LONG_ARRAY; + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/data/BlockStatesData.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/data/BlockStatesData.java new file mode 100644 index 00000000..eecc3f57 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/data/BlockStatesData.java @@ -0,0 +1,15 @@ +package de.bluecolored.bluemap.core.mca.data; + +import de.bluecolored.bluemap.core.world.BlockState; +import lombok.Getter; + +@Getter +@SuppressWarnings("FieldMayBeFinal") +public class BlockStatesData { + private static final long[] EMPTY_LONG_ARRAY = new long[0]; + private static final BlockState[] EMPTY_BLOCKSTATE_ARRAY = new BlockState[0]; + + private BlockState[] palette = EMPTY_BLOCKSTATE_ARRAY; + private long[] data = EMPTY_LONG_ARRAY; + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/data/ChunkData.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/data/ChunkData.java new file mode 100644 index 00000000..d0327888 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/data/ChunkData.java @@ -0,0 +1,17 @@ +package de.bluecolored.bluemap.core.mca.data; + +import lombok.Getter; +import org.jetbrains.annotations.Nullable; + +@Getter +@SuppressWarnings("FieldMayBeFinal") +public class ChunkData { + private static final HeightmapsData EMPTY_HEIGHTMAPS_DATA = new HeightmapsData(); + + private int dataVersion = 0; + private String status = "none"; + private long inhabitedTime = 0; + private HeightmapsData heightmaps = EMPTY_HEIGHTMAPS_DATA; + private @Nullable SectionData[] sections = null; + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/data/HeightmapsData.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/data/HeightmapsData.java new file mode 100644 index 00000000..f55ce013 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/data/HeightmapsData.java @@ -0,0 +1,17 @@ +package de.bluecolored.bluemap.core.mca.data; + +import de.bluecolored.bluenbt.NBTName; +import lombok.Getter; + +@Getter +@SuppressWarnings("FieldMayBeFinal") +public class HeightmapsData { + private static final long[] EMPTY_LONG_ARRAY = new long[0]; + + @NBTName("WORLD_SURFACE") + private long[] worldSurface = EMPTY_LONG_ARRAY; + + @NBTName("OCEAN_FLOOR") + private long[] oceanFloor = EMPTY_LONG_ARRAY; + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/data/LevelData.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/data/LevelData.java new file mode 100644 index 00000000..cf9fe59f --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/data/LevelData.java @@ -0,0 +1,19 @@ +package de.bluecolored.bluemap.core.mca.data; + +import lombok.Getter; + +@Getter +@SuppressWarnings("FieldMayBeFinal") +public class LevelData { + + private Data data = new Data(); + + @Getter + public static class Data { + + private String levelName = "world"; + private int spawnX = 0, spawnY = 0, spawnZ = 0; + + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/data/SectionData.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/data/SectionData.java new file mode 100644 index 00000000..b1b30666 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/data/SectionData.java @@ -0,0 +1,20 @@ +package de.bluecolored.bluemap.core.mca.data; + +import de.bluecolored.bluenbt.NBTName; +import lombok.Getter; + +@Getter +@SuppressWarnings("FieldMayBeFinal") +public class SectionData { + private static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; + private static final BlockStatesData EMPTY_BLOCKSTATESDATA = new BlockStatesData(); + private static final BiomesData EMPTY_BIOMESDATA = new BiomesData(); + + private int y = 0; + private byte[] blockLight = EMPTY_BYTE_ARRAY; + private byte[] skyLight = EMPTY_BYTE_ARRAY; + @NBTName("block_states") + private BlockStatesData blockStates = EMPTY_BLOCKSTATESDATA; + private BiomesData biomes = EMPTY_BIOMESDATA; + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/deserializer/BlockStateDeserializer.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/deserializer/BlockStateDeserializer.java new file mode 100644 index 00000000..13fdff7a --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/deserializer/BlockStateDeserializer.java @@ -0,0 +1,44 @@ +package de.bluecolored.bluemap.core.mca.deserializer; + +import de.bluecolored.bluemap.core.world.BlockState; +import de.bluecolored.bluenbt.NBTReader; +import de.bluecolored.bluenbt.TypeDeserializer; + +import java.io.IOException; +import java.util.LinkedHashMap; +import java.util.Map; + +public class BlockStateDeserializer implements TypeDeserializer { + + @Override + public BlockState read(NBTReader reader) throws IOException { + reader.beginCompound(); + + String id = null; + Map properties = null; + + while (reader.hasNext()) { + String name = reader.name(); + if (name.equals("Name")){ + id = reader.nextString(); + } else if (name.equals("Properties")) { + properties = new LinkedHashMap<>(); + reader.beginCompound(); + while (reader.hasNext()) + properties.put(reader.name(), reader.nextString()); + reader.endCompound(); + } else { + reader.skip(); + } + } + + reader.endCompound(); + + if (id == null) throw new IOException("Invalid BlockState, Name is missing!"); + + if (properties == null) + return new BlockState(id); + return new BlockState(id, properties); + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/region/LinearRegion.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/region/LinearRegion.java index 05d3cd98..ead1c2df 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/region/LinearRegion.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/region/LinearRegion.java @@ -27,13 +27,13 @@ import com.flowpowered.math.vector.Vector2i; import de.bluecolored.bluemap.core.logger.Logger; import de.bluecolored.bluemap.core.mca.MCAChunk; +import de.bluecolored.bluemap.core.mca.MCAMath; import de.bluecolored.bluemap.core.mca.MCAWorld; +import de.bluecolored.bluemap.core.mca.data.ChunkData; import de.bluecolored.bluemap.core.world.Chunk; import de.bluecolored.bluemap.core.world.EmptyChunk; import de.bluecolored.bluemap.core.world.Region; import io.airlift.compress.zstd.ZstdInputStream; -import net.querz.nbt.CompoundTag; -import net.querz.nbt.Tag; import java.io.ByteArrayInputStream; import java.io.DataInputStream; @@ -120,14 +120,8 @@ public Chunk loadChunk(int chunkX, int chunkZ, boolean ignoreMissingLightData) t dis.skipBytes(((1024 - pos - 1) << 3) + 4); // Skip current chunk 0 and unneeded other chunks zero/size dis.skipBytes(skip); // Skip unneeded chunks data - Tag tag = Tag.deserialize(dis, Tag.DEFAULT_MAX_DEPTH); - if (tag instanceof CompoundTag) { - MCAChunk chunk = MCAChunk.create(world, (CompoundTag) tag); - if (!chunk.isGenerated()) return EmptyChunk.INSTANCE; - return chunk; - } else { - throw new IOException("Invalid data tag: " + (tag == null ? "null" : tag.getClass().getName())); - } + ChunkData chunkData = MCAMath.BLUENBT.read(dis, ChunkData.class); + return MCAChunk.create(world, chunkData); } } catch (RuntimeException e) { throw new IOException(e); diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/region/MCARegion.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/region/MCARegion.java index 202ea0d1..cfdfc674 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/region/MCARegion.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/region/MCARegion.java @@ -27,13 +27,13 @@ import com.flowpowered.math.vector.Vector2i; import de.bluecolored.bluemap.core.logger.Logger; import de.bluecolored.bluemap.core.mca.MCAChunk; +import de.bluecolored.bluemap.core.mca.MCAMath; import de.bluecolored.bluemap.core.mca.MCAWorld; +import de.bluecolored.bluemap.core.mca.data.ChunkData; +import de.bluecolored.bluemap.core.storage.Compression; import de.bluecolored.bluemap.core.world.Chunk; import de.bluecolored.bluemap.core.world.EmptyChunk; import de.bluecolored.bluemap.core.world.Region; -import net.querz.nbt.CompoundTag; -import net.querz.nbt.Tag; -import net.querz.nbt.mca.CompressionType; import java.io.*; import java.nio.file.Files; @@ -86,25 +86,23 @@ public Chunk loadChunk(int chunkX, int chunkZ, boolean ignoreMissingLightData) t raf.seek(offset + 4); // +4 skip chunk size - byte compressionTypeByte = raf.readByte(); - CompressionType compressionType = compressionTypeByte == 3 ? - CompressionType.NONE : - CompressionType.getFromID(compressionTypeByte); - if (compressionType == null) { - throw new IOException("Invalid compression type " + compressionTypeByte); + byte compressionByte = raf.readByte(); + Compression compression; + switch (compressionByte) { + case 0: + case 3: compression = Compression.NONE; break; + case 1: compression = Compression.GZIP; break; + case 2: compression = Compression.DEFLATE; break; + default: throw new IOException("Invalid compression type " + compressionByte); } - DataInputStream dis = new DataInputStream(new BufferedInputStream(compressionType.decompress(new FileInputStream(raf.getFD())))); - Tag tag = Tag.deserialize(dis, Tag.DEFAULT_MAX_DEPTH); - if (tag instanceof CompoundTag) { - MCAChunk chunk = MCAChunk.create(world, (CompoundTag) tag); - if (!chunk.isGenerated()) return EmptyChunk.INSTANCE; - return chunk; - } else { - throw new IOException("Invalid data tag: " + (tag == null ? "null" : tag.getClass().getName())); - } + DataInputStream dis = new DataInputStream(new BufferedInputStream(compression.decompress(new FileInputStream(raf.getFD())))); + ChunkData chunkData = MCAMath.BLUENBT.read(dis, ChunkData.class); + return MCAChunk.create(world, chunkData); } catch (RuntimeException e) { + Logger.global.logError("Failed to load Chunk!", e); + throw new IOException(e); } } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/resourcepack/Dimension.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/resourcepack/Dimension.java new file mode 100644 index 00000000..cf050a19 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/resourcepack/Dimension.java @@ -0,0 +1,16 @@ +package de.bluecolored.bluemap.core.mca.resourcepack; + +import de.bluecolored.bluemap.api.debug.DebugDump; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@NoArgsConstructor +@AllArgsConstructor +@Getter +@DebugDump +public class Dimension { + + private String type = "minecraft:overworld"; + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/resourcepack/DimensionType.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/resourcepack/DimensionType.java new file mode 100644 index 00000000..d3df1f97 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/resourcepack/DimensionType.java @@ -0,0 +1,65 @@ +package de.bluecolored.bluemap.core.mca.resourcepack; + +import de.bluecolored.bluemap.api.debug.DebugDump; +import lombok.*; + +@NoArgsConstructor +@AllArgsConstructor +@Getter +@DebugDump +public class DimensionType { + + private static final DimensionType OVERWORLD = new DimensionType(); + private static final DimensionType NETHER = new DimensionType( + true, + false, + 8.0, + false, + true, + 0.1f, + 128, + 0, + 256, + "#minecraft:infiniburn_nether", + "minecraft:the_nether" + ); + private static final DimensionType END = new DimensionType( + false, + false, + 1.0, + false, + false, + 0, + 256, + 0, + 256, + "#minecraft:infiniburn_end", + "minecraft:the_end" + ); + private static final DimensionType OVERWORLD_CAVES = new DimensionType( + false, + true, + 1.0, + true, + true, + 0, + 256, + -64, + 384, + "#minecraft:infiniburn_overworld", + "minecraft:overworld" + ); + + private boolean ultrawarm = false; + private boolean natural = true; + private double coordinateScale = 1.0; + private boolean hasSkylight = true; + private boolean hasCeiling = false; + private float ambientLight = 0; + private int logicalHeight = 256; + private int minY = -64; + private int height = 384; + private String infiniburn = "#minecraft:infiniburn_overworld"; + private String effects = "minecraft:overworld"; + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/Compression.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/Compression.java index 463e7c68..6028e524 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/Compression.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/Compression.java @@ -31,16 +31,13 @@ import java.io.InputStream; import java.io.OutputStream; import java.util.NoSuchElementException; -import java.util.zip.DeflaterInputStream; -import java.util.zip.DeflaterOutputStream; -import java.util.zip.GZIPInputStream; -import java.util.zip.GZIPOutputStream; +import java.util.zip.*; public enum Compression { NONE("none", "", out -> out, in -> in), GZIP("gzip", ".gz", GZIPOutputStream::new, GZIPInputStream::new), - DEFLATE("deflate", ".deflate", DeflaterOutputStream::new, DeflaterInputStream::new), + DEFLATE("deflate", ".deflate", DeflaterOutputStream::new, InflaterInputStream::new), ZSTD("zstd", ".zst", ZstdOutputStream::new, ZstdInputStream::new); private final String typeId;