From ff5ccababd1855ad5314d8fda45a4c22a900d942 Mon Sep 17 00:00:00 2001 From: Querz Date: Mon, 24 Jun 2019 19:59:46 +0200 Subject: [PATCH 1/7] handle empty sections --- src/main/java/net/querz/nbt/mca/Chunk.java | 10 +++++++++- src/main/java/net/querz/nbt/mca/Section.java | 12 ++++++++++-- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/main/java/net/querz/nbt/mca/Chunk.java b/src/main/java/net/querz/nbt/mca/Chunk.java index 3f0ae94d..cd53152a 100644 --- a/src/main/java/net/querz/nbt/mca/Chunk.java +++ b/src/main/java/net/querz/nbt/mca/Chunk.java @@ -73,7 +73,15 @@ private void initReferences() { this.structures = level.getCompoundTag("Structures"); if (level.containsKey("Sections")) { for (CompoundTag section : level.getListTag("Sections").asCompoundTagList()) { - this.sections[section.getByte("Y")] = new Section(section); + int sectionIndex = section.getByte("Y"); + if (sectionIndex > 15 || sectionIndex < 0) { + continue; + } + Section newSection = new Section(section); + if (newSection.isEmpty()) { + continue; + } + this.sections[sectionIndex] = newSection; } } } diff --git a/src/main/java/net/querz/nbt/mca/Section.java b/src/main/java/net/querz/nbt/mca/Section.java index e94ea164..0ec467f6 100644 --- a/src/main/java/net/querz/nbt/mca/Section.java +++ b/src/main/java/net/querz/nbt/mca/Section.java @@ -17,8 +17,11 @@ public class Section { private byte[] skyLight; public Section(CompoundTag sectionRoot) { - data = sectionRoot; - palette = sectionRoot.getListTag("Palette").asCompoundTagList(); + ListTag rawPalette = sectionRoot.getListTag("Palette"); + if (rawPalette == null) { + return; + } + palette = rawPalette.asCompoundTagList(); for (int i = 0; i < palette.size(); i++) { CompoundTag data = palette.get(i); putValueIndexedPalette(data, i); @@ -26,6 +29,7 @@ public Section(CompoundTag sectionRoot) { blockLight = sectionRoot.getByteArray("BlockLight"); blockStates = sectionRoot.getLongArray("BlockStates"); skyLight = sectionRoot.getByteArray("SkyLight"); + data = sectionRoot; } Section() {} @@ -72,6 +76,10 @@ private class PaletteIndex { } } + public boolean isEmpty() { + return data == null; + } + public CompoundTag getBlockStateAt(int blockX, int blockY, int blockZ) { int index = getBlockIndex(blockX, blockY, blockZ); int paletteIndex = getPaletteIndex(index); From 94ed6d37ea3d359374e797c36492f7b0ba781b54 Mon Sep 17 00:00:00 2001 From: Jochem Broekhoff Date: Sat, 6 Jul 2019 13:25:33 +0200 Subject: [PATCH 2/7] Added Automatic-Module-Name manifest attribute. --- build.gradle | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/build.gradle b/build.gradle index aee3f244..7e6798e3 100644 --- a/build.gradle +++ b/build.gradle @@ -53,3 +53,9 @@ artifacts { archives sourcesJar archives javadocJar } + +jar { + manifest { + attributes('Automatic-Module-Name': 'net.querz.nbt') + } +} From a28953364faa0be9f58c33c1b321408a40562bd5 Mon Sep 17 00:00:00 2001 From: Querz Date: Wed, 7 Aug 2019 09:35:49 +0200 Subject: [PATCH 3/7] update version to 4.1 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 7e6798e3..c992faa4 100644 --- a/build.gradle +++ b/build.gradle @@ -9,7 +9,7 @@ apply plugin: 'jacoco' group = 'net.querz.nbt' archivesBaseName = 'nbt' -version = '4.0' +version = '4.1' sourceCompatibility = '1.8' targetCompatibility = '1.8' compileJava.options.encoding = 'UTF-8' From d39d8ef2a39fa3ba4971b002402529b883c1ff51 Mon Sep 17 00:00:00 2001 From: Querz Date: Wed, 7 Aug 2019 10:56:29 +0200 Subject: [PATCH 4/7] use trusty dist for travis --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index ec8ab436..dc52328b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,3 +1,4 @@ +dist: trusty language: java jdk: - oraclejdk8 From badcf2dd66d682420ba553bfddc55875363c1773 Mon Sep 17 00:00:00 2001 From: Querz Date: Thu, 26 Sep 2019 17:24:35 +0200 Subject: [PATCH 5/7] add javadoc to mca library and make some functions public --- .gitignore | 5 +- src/main/java/net/querz/nbt/mca/Chunk.java | 183 +++++++++++++++++++ src/main/java/net/querz/nbt/mca/MCAFile.java | 46 +++++ src/main/java/net/querz/nbt/mca/Section.java | 87 ++++++++- 4 files changed, 312 insertions(+), 9 deletions(-) diff --git a/.gitignore b/.gitignore index 3e53b2cc..ad25607b 100644 --- a/.gitignore +++ b/.gitignore @@ -81,4 +81,7 @@ local.properties doc/ # ignore out -out/ \ No newline at end of file +out/ + +# ignore patches +*.patch \ No newline at end of file diff --git a/src/main/java/net/querz/nbt/mca/Chunk.java b/src/main/java/net/querz/nbt/mca/Chunk.java index cd53152a..5ab0fe51 100644 --- a/src/main/java/net/querz/nbt/mca/Chunk.java +++ b/src/main/java/net/querz/nbt/mca/Chunk.java @@ -42,6 +42,10 @@ public class Chunk { this.lastMCAUpdate = lastMCAUpdate; } + /** + * Create a new chunk based on raw base data from a region file. + * @param data The raw base data to be used. + */ public Chunk(CompoundTag data) { this.data = data; initReferences(); @@ -86,6 +90,14 @@ private void initReferences() { } } + /** + * Serializes this chunk to a RandomAccessFile. + * @param raf The RandomAccessFile to be written to. + * @param xPos The x-coordinate of the chunk. + * @param zPos The z-coodrinate of the chunk. + * @return The amount of bytes written to the RandomAccessFile. + * @throws IOException When something went wrong during writing. + */ public int serialize(RandomAccessFile raf, int xPos, int zPos) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(4096); try (DataOutputStream nbtOut = new DataOutputStream(new BufferedOutputStream(CompressionType.ZLIB.compress(baos)))) { @@ -98,6 +110,11 @@ public int serialize(RandomAccessFile raf, int xPos, int zPos) throws IOExceptio return rawData.length + 5; } + /** + * Reads chunk data from a RandomAccessFile. The RandomAccessFile must already be at the correct position. + * @param raf The RandomAccessFile to read the chunk data from. + * @throws IOException When something went wrong during reading. + */ public void deserialize(RandomAccessFile raf) throws IOException { byte compressionTypeByte = raf.readByte(); CompressionType compressionType = CompressionType.getFromID(compressionTypeByte); @@ -114,6 +131,13 @@ public void deserialize(RandomAccessFile raf) throws IOException { } } + /** + * Fetches a biome id at a specific block column in this chunk. + * The coordinates can be absolute coordinates or relative to the region or chunk. + * @param blockX The x-coordinate of the block column. + * @param blockZ The z-coordinate of the block column. + * @return The biome id or -1 if the biomes are not correctly initialized. + */ public int getBiomeAt(int blockX, int blockZ) { if (biomes == null || biomes.length != 256) { return -1; @@ -121,6 +145,14 @@ public int getBiomeAt(int blockX, int blockZ) { return biomes[getBlockIndex(blockX, blockZ)]; } + /** + * Sets a biome id at a specific block column. + * The coordinates can be absolute coordinates or relative to the region or chunk. + * @param blockX The x-coordinate of the block column. + * @param blockZ The z-coordinate of the block column. + * @param biomeID The biome id to be set. + * When set to a negative number, Minecraft will replace it with the block column's default biome. + */ public void setBiomeAt(int blockX, int blockZ, int biomeID) { if (biomes == null || biomes.length != 256) { biomes = new int[256]; @@ -131,6 +163,14 @@ public void setBiomeAt(int blockX, int blockZ, int biomeID) { biomes[getBlockIndex(blockX, blockZ)] = biomeID; } + /** + * Fetches the block state at a specific block location in this chunk. + * The block coordinate can be absolute or relative to the region or chunk. + * @param blockX The x-coordinate of the block. + * @param blockY The y-coordinate of the block. + * @param blockZ The z-coordinate of the block. + * @return The block state or null if a section at the location does not exist. + */ public CompoundTag getBlockStateAt(int blockX, int blockY, int blockZ) { Section section = sections[MCAUtil.blockToChunk(blockY)]; if (section == null) { @@ -139,6 +179,17 @@ public CompoundTag getBlockStateAt(int blockX, int blockY, int blockZ) { return section.getBlockStateAt(blockX, blockY, blockZ); } + /** + * Sets a block state at a specific location. + * The block coordinates can be absolute or relative to the region or chunk. + * @param blockX The x-coordinate of the block. + * @param blockY The y-coordinate of the block. + * @param blockZ The z-coordinate of the block. + * @param state The block state to be set. + * @param cleanup When true, it will cleanup all palettes of this chunk. + * This option should only be used moderately to avoid unnecessary recalculation of the palette indices. + * Recalculating the Palette should only be executed once right before saving the Chunk to file. + */ public void setBlockStateAt(int blockX, int blockY, int blockZ, CompoundTag state, boolean cleanup) { int sectionIndex = MCAUtil.blockToChunk(blockY); Section section = sections[sectionIndex]; @@ -148,58 +199,113 @@ public void setBlockStateAt(int blockX, int blockY, int blockZ, CompoundTag stat section.setBlockStateAt(blockX, blockY, blockZ, state, cleanup); } + /** + * @return The DataVersion of this chunk. + */ public int getDataVersion() { return dataVersion; } + /** + * Sets the DataVersion of this chunk. This does not check if the data of this chunk conforms + * to that DataVersion, that is the responsibility of the developer. + * @param dataVersion The DataVersion to be set. + */ public void setDataVersion(int dataVersion) { this.dataVersion = dataVersion; } + /** + * @return The timestamp when this region file was last updated in seconds since 1970-01-01. + */ public int getLastMCAUpdate() { return lastMCAUpdate; } + /** + * Sets the timestamp when this region file was last updated in seconds since 1970-01-01. + * @param lastMCAUpdate The time in seconds since 1970-01-01. + */ public void setLastMCAUpdate(int lastMCAUpdate) { this.lastMCAUpdate = lastMCAUpdate; } + /** + * @return The generation station of this chunk. + */ public String getStatus() { return status; } + /** + * Sets the generation status of this chunk. + * @param status The generation status of this chunk. + */ public void setStatus(String status) { this.status = status; } + /** + * Fetches the section at the given y-coordinate. + * @param sectionY The y-coordinate of the section in this chunk ranging from 0 to 15. + * @return The Section. + */ public Section getSection(int sectionY) { return sections[sectionY]; } + /** + * Sets a section at a givesn y-coordinate + * @param sectionY The y-coordinate of the section in this chunk ranging from 0 to 15. + * @param section The section to be set. + */ public void setSection(int sectionY, Section section) { sections[sectionY] = section; } + /** + * @return The timestamp when this chunk was last updated as a UNIX timestamp. + */ public long getLastUpdate() { return lastUpdate; } + /** + * Sets the time when this chunk was last updated as a UNIX timestamp. + * @param lastUpdate The UNIX timestamp. + */ public void setLastUpdate(long lastUpdate) { this.lastUpdate = lastUpdate; } + /** + * @return The cumulative amount of time players have spent in this chunk in ticks. + */ public long getInhabitedTime() { return inhabitedTime; } + /** + * Sets the cumulative amount of time players have spent in this chunk in ticks. + * @param inhabitedTime The time in ticks. + */ public void setInhabitedTime(long inhabitedTime) { this.inhabitedTime = inhabitedTime; } + /** + * @return A matrix of biome IDs for all block columns in this chunk. + */ public int[] getBiomes() { return biomes; } + /** + * Sets the biome IDs for this chunk. + * @param biomes The biome ID matrix of this chunk. Must have a length of 256. + * @throws IllegalArgumentException When the biome matrix does not have a length of 256 + * or is null + */ public void setBiomes(int[] biomes) { if (biomes != null && biomes.length != 256) { throw new IllegalArgumentException("biomes array must have a length of 256"); @@ -207,90 +313,167 @@ public void setBiomes(int[] biomes) { this.biomes = biomes; } + /** + * @return The height maps of this chunk. + */ public CompoundTag getHeightMaps() { return heightMaps; } + /** + * Sets the height maps of this chunk. + * @param heightMaps The height maps. + */ public void setHeightMaps(CompoundTag heightMaps) { this.heightMaps = heightMaps; } + /** + * @return The carving masks of this chunk. + */ public CompoundTag getCarvingMasks() { return carvingMasks; } + /** + * Sets the carving masks of this chunk. + * @param carvingMasks The carving masks. + */ public void setCarvingMasks(CompoundTag carvingMasks) { this.carvingMasks = carvingMasks; } + /** + * @return The entities of this chunk. + */ public ListTag getEntities() { return entities; } + /** + * Sets the entities of this chunk. + * @param entities The entities. + */ public void setEntities(ListTag entities) { this.entities = entities; } + /** + * @return The tile entities of this chunk. + */ public ListTag getTileEntities() { return tileEntities; } + /** + * Sets the tile entities of this chunk. + * @param tileEntities The tile entities of this chunk. + */ public void setTileEntities(ListTag tileEntities) { this.tileEntities = tileEntities; } + /** + * @return The tile ticks of this chunk. + */ public ListTag getTileTicks() { return tileTicks; } + /** + * Sets the tile ticks of this chunk. + * @param tileTicks Thee tile ticks. + */ public void setTileTicks(ListTag tileTicks) { this.tileTicks = tileTicks; } + /** + * @return The liquid ticks of this chunk. + */ public ListTag getLiquidTicks() { return liquidTicks; } + /** + * Sets the liquid ticks of this chunk. + * @param liquidTicks The liquid ticks. + */ public void setLiquidTicks(ListTag liquidTicks) { this.liquidTicks = liquidTicks; } + /** + * @return The light sources in this chunk. + */ public ListTag> getLights() { return lights; } + /** + * Sets the light sources in this chunk. + * @param lights The light sources. + */ public void setLights(ListTag> lights) { this.lights = lights; } + /** + * @return THe liquids to be ticked in this chunk. + */ public ListTag> getLiquidsToBeTicked() { return liquidsToBeTicked; } + /** + * Sets the liquids to be ticked in this chunk. + * @param liquidsToBeTicked The liquids to be ticked. + */ public void setLiquidsToBeTicked(ListTag> liquidsToBeTicked) { this.liquidsToBeTicked = liquidsToBeTicked; } + /** + * @return Stuff to be ticked in this chunk. + */ public ListTag> getToBeTicked() { return toBeTicked; } + /** + * Sets stuff to be ticked in this chunk. + * @param toBeTicked The stuff to be ticked. + */ public void setToBeTicked(ListTag> toBeTicked) { this.toBeTicked = toBeTicked; } + /** + * @return Things that are in post processing in this chunk. + */ public ListTag> getPostProcessing() { return postProcessing; } + /** + * Sets things to be post processed in this chunk. + * @param postProcessing The things to be post processed. + */ public void setPostProcessing(ListTag> postProcessing) { this.postProcessing = postProcessing; } + /** + * @return Data about structures in this chunk. + */ public CompoundTag getStructures() { return structures; } + /** + * Sets data about structures in this chunk. + * @param structures The data about structures. + */ public void setStructures(CompoundTag structures) { this.structures = structures; } diff --git a/src/main/java/net/querz/nbt/mca/MCAFile.java b/src/main/java/net/querz/nbt/mca/MCAFile.java index 7ca35c02..2ed23de4 100644 --- a/src/main/java/net/querz/nbt/mca/MCAFile.java +++ b/src/main/java/net/querz/nbt/mca/MCAFile.java @@ -124,6 +124,12 @@ public int serialize(RandomAccessFile raf, boolean changeLastUpdate) throws IOEx return chunksWritten; } + /** + * Set a specific Chunk at a specific index. The index must be in range of 0 - 1023. + * @param index The index of the Chunk. + * @param chunk The Chunk to be set. + * @throws IndexOutOfBoundsException If index is not in the range. + */ public void setChunk(int index, Chunk chunk) { checkIndex(index); if (chunks == null) { @@ -132,6 +138,13 @@ public void setChunk(int index, Chunk chunk) { chunks[index] = chunk; } + /** + * Set a specific Chunk at a specific chunk location. + * The x- and z-value can be absolute chunk coordinates or they can be relative to the region origin. + * @param chunkX The x-coordinate of the Chunk. + * @param chunkZ The z-coordinate of the Chunk. + * @param chunk The chunk to be set. + */ public void setChunk(int chunkX, int chunkZ, Chunk chunk) { setChunk(getChunkIndex(chunkX, chunkZ), chunk); } @@ -187,10 +200,23 @@ private Chunk createChunkIfMissing(int blockX, int blockZ) { return chunk; } + /** + * Sets the biome at a specific block column. + * A negative number will be replaced by the columns default biome when loaded by Minecraft. + * @param blockX The x-coordinate of the block column. + * @param blockZ The z-coordinate of the block column. + * @param biomeID The biome id to be set. + */ public void setBiomeAt(int blockX, int blockZ, int biomeID) { createChunkIfMissing(blockX, blockZ).setBiomeAt(blockX, blockZ, biomeID); } + /** + * Fetches the biome id at a specific block column. + * @param blockX The x-coordinate of the block column. + * @param blockZ The z-coordinate of the block column. + * @return The biome id if the chunk exists and the chunk has biomes, otherwise -1. + */ public int getBiomeAt(int blockX, int blockZ) { int chunkX = MCAUtil.blockToChunk(blockX), chunkZ = MCAUtil.blockToChunk(blockZ); Chunk chunk = getChunk(getChunkIndex(chunkX, chunkZ)); @@ -200,10 +226,27 @@ public int getBiomeAt(int blockX, int blockZ) { return chunk.getBiomeAt(blockX, blockZ); } + /** + * Set a block state at a specific block location. + * The block coordinates can be absolute coordinates or they can be relative to the region. + * @param blockX The x-coordinate of the block. + * @param blockY The y-coordinate of the block. + * @param blockZ The z-coordinate of the block. + * @param state The block state to be set. + * @param cleanup Whether the Palette and the BLockStates should be recalculated after adding the block state. + */ public void setBlockStateAt(int blockX, int blockY, int blockZ, CompoundTag state, boolean cleanup) { createChunkIfMissing(blockX, blockZ).setBlockStateAt(blockX, blockY, blockZ, state, cleanup); } + /** + * Fetches a block state at a specific block location. + * The block coordinates can be absolute coordinates or they can be relative to the region. + * @param blockX The x-coordinate of the block. + * @param blockY The y-coordinate of the block. + * @param blockZ The z-coordinate of the block. + * @return The block state or null if the chunk or the section do not exist. + */ public CompoundTag getBlockStateAt(int blockX, int blockY, int blockZ) { int chunkX = MCAUtil.blockToChunk(blockX), chunkZ = MCAUtil.blockToChunk(blockZ); Chunk chunk = getChunk(chunkX, chunkZ); @@ -213,6 +256,9 @@ public CompoundTag getBlockStateAt(int blockX, int blockY, int blockZ) { return chunk.getBlockStateAt(blockX, blockY, blockZ); } + /** + * Recalculates the Palette and the BlockStates of all chunks and sections of this region. + */ public void cleanupPalettesAndBlockStates() { for (Chunk chunk : chunks) { if (chunk != null) { diff --git a/src/main/java/net/querz/nbt/mca/Section.java b/src/main/java/net/querz/nbt/mca/Section.java index 0ec467f6..649a40d3 100644 --- a/src/main/java/net/querz/nbt/mca/Section.java +++ b/src/main/java/net/querz/nbt/mca/Section.java @@ -16,6 +16,10 @@ public class Section { private long[] blockStates; private byte[] skyLight; + /** + * Creates a new Section based on raw section data. + * @param sectionRoot The raw section data + */ public Section(CompoundTag sectionRoot) { ListTag rawPalette = sectionRoot.getListTag("Palette"); if (rawPalette == null) { @@ -34,7 +38,7 @@ public Section(CompoundTag sectionRoot) { Section() {} - void putValueIndexedPalette(CompoundTag data, int index) { + private void putValueIndexedPalette(CompoundTag data, int index) { PaletteIndex leaf = new PaletteIndex(data, index); String name = data.getString("Name"); List leaves = valueIndexedPalette.get(name); @@ -52,7 +56,7 @@ void putValueIndexedPalette(CompoundTag data, int index) { } } - PaletteIndex getValueIndexedPalette(CompoundTag data) { + private PaletteIndex getValueIndexedPalette(CompoundTag data) { List leaves = valueIndexedPalette.get(data.getString("Name")); if (leaves == null) { return null; @@ -76,16 +80,38 @@ private class PaletteIndex { } } + /** + * Checks whether the data of this Section is empty. + * @return true if empty + */ public boolean isEmpty() { return data == null; } + /** + * Fetches a block state based on a block location from this section. + * The coordinates represent the location of the block inside of this Section. + * @param blockX The x-coordinate of the block in this Section + * @param blockY The y-coordinate of the block in this Section + * @param blockZ The z-coordinate of the block in this Section + * @return The block state data of this block. + */ public CompoundTag getBlockStateAt(int blockX, int blockY, int blockZ) { int index = getBlockIndex(blockX, blockY, blockZ); int paletteIndex = getPaletteIndex(index); return palette.get(paletteIndex); } + /** + * Attempts to add a block state for a specific block location in this Section. + * @param blockX The x-coordinate of the block in this Section + * @param blockY The y-coordinate of the block in this Section + * @param blockZ The z-coordinate of the block in this Section + * @param state The block state to be set + * @param cleanup When true, it will cleanup the palette of this section. + * This option should only be used moderately to avoid unnecessary recalculation of the palette indices. + * Recalculating the Palette should only be executed once right before saving the Section to file. + */ public void setBlockStateAt(int blockX, int blockY, int blockZ, CompoundTag state, boolean cleanup) { int paletteSizeBefore = palette.size(); int paletteIndex = addToPalette(state); @@ -142,7 +168,11 @@ public void setPaletteIndex(int blockIndex, int paletteIndex, long[] blockStates } } - ListTag getPalette() { + /** + * Fetches the palette of this Section. + * @return The palette of this Section. + */ + public ListTag getPalette() { return palette; } @@ -156,27 +186,32 @@ int addToPalette(CompoundTag data) { return palette.size() - 1; } - int getBlockIndex(int blockX, int blockY, int blockZ) { + private int getBlockIndex(int blockX, int blockY, int blockZ) { return (blockY & 0xF) * 256 + (blockZ & 0xF) * 16 + (blockX & 0xF); } - static long updateBits(long n, long m, int i, int j) { + private static long updateBits(long n, long m, int i, int j) { //replace i to j in n with j - i bits of m long mShifted = i > 0 ? (m & ((1L << j - i) - 1)) << i : (m & ((1L << j - i) - 1)) >>> -i; return ((n & ((j > 63 ? 0 : (~0L << j)) | (i < 0 ? 0 : ((1L << i) - 1L)))) | mShifted); } - static long bitRange(long value, int from, int to) { + private static long bitRange(long value, int from, int to) { int waste = 64 - to; return (value << waste) >>> (waste + from); } + /** + * This method recalculates the palette and its indices. + * This should only be used moderately to avoid unnecessary recalculation of the palette indices. + * Recalculating the Palette should only be executed once right before saving the Section to file. + */ public void cleanupPaletteAndBlockStates() { Map oldToNewMapping = cleanupPalette(); adjustBlockStateBits(oldToNewMapping, blockStates); } - Map cleanupPalette() { + private Map cleanupPalette() { //create index - palette mapping Map allIndices = new HashMap<>(); for (int i = 0; i < 4096; i++) { @@ -202,7 +237,7 @@ Map cleanupPalette() { return allIndices; } - void adjustBlockStateBits(Map oldToNewMapping, long[] blockStates) { + private void adjustBlockStateBits(Map oldToNewMapping, long[] blockStates) { //increases or decreases the amount of bits used per BlockState //based on the size of the palette. oldToNewMapping can be used to update indices //if the palette had been cleaned up before using MCAFile#cleanupPalette(). @@ -223,10 +258,18 @@ void adjustBlockStateBits(Map oldToNewMapping, long[] blockSta this.blockStates = newBlockStates; } + /** + * @return The block light array of this Section + */ public byte[] getBlockLight() { return blockLight; } + /** + * Sets the block light array for this section. + * @param blockLight The block light array + * @throws IllegalArgumentException When the length of the array is not 2048 + */ public void setBlockLight(byte[] blockLight) { if (blockLight != null && blockLight.length != 2048) { throw new IllegalArgumentException("BlockLight array must have a length of 2048"); @@ -234,10 +277,19 @@ public void setBlockLight(byte[] blockLight) { this.blockLight = blockLight; } + /** + * @return The indices of the block states of this Section. + */ public long[] getBlockStates() { return blockStates; } + /** + * Sets the block state indices to a custom value. + * @param blockStates The block state indices. + * @throws NullPointerException If blockStates is null + * @throws IllegalArgumentException When blockStates' length is < 256 or > 4096 and is not a multiple of 64 + */ public void setBlockStates(long[] blockStates) { if (blockStates == null) { throw new NullPointerException("BlockStates cannot be null"); @@ -247,10 +299,18 @@ public void setBlockStates(long[] blockStates) { this.blockStates = blockStates; } + /** + * @return The sky light values of this Section + */ public byte[] getSkyLight() { return skyLight; } + /** + * Sets the sky light values of this section. + * @param skyLight The custom sky light values + * @throws IllegalArgumentException If the length of the array is not 2048 + */ public void setSkyLight(byte[] skyLight) { if (skyLight != null && skyLight.length != 2048) { throw new IllegalArgumentException("SkyLight array must have a length of 2048"); @@ -258,6 +318,10 @@ public void setSkyLight(byte[] skyLight) { this.skyLight = skyLight; } + /** + * Creates an empty Section with base values. + * @return An empty Section + */ public static Section newSection() { Section s = new Section(); s.blockStates = new long[256]; @@ -269,6 +333,13 @@ public static Section newSection() { return s; } + /** + * Updates the raw CompoundTag that this Section is based on. + * This must be called before saving a Section to disk if the Section was manually created + * to set the Y of this Section. + * @param y The Y-value of this Section + * @return A reference to the raw CompoundTag this Section is based on + */ public CompoundTag updateHandle(int y) { data.putByte("Y", (byte) y); data.put("Palette", palette); From b80ee75c70c5f03c214113c1007998068925d205 Mon Sep 17 00:00:00 2001 From: Querz Date: Mon, 30 Sep 2019 17:11:13 +0200 Subject: [PATCH 6/7] unsafely adding a tag to an empty listtag should set the type correctly --- src/main/java/net/querz/nbt/ListTag.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/net/querz/nbt/ListTag.java b/src/main/java/net/querz/nbt/ListTag.java index 454b3aab..dd54744c 100644 --- a/src/main/java/net/querz/nbt/ListTag.java +++ b/src/main/java/net/querz/nbt/ListTag.java @@ -116,7 +116,7 @@ public void add(T t) { public void add(int index, T t) { Objects.requireNonNull(t); getValue().add(index, t); - if (typeClass == null) { + if (typeClass == null || typeClass == EndTag.class) { typeClass = t.getClass(); } } From b56ad64dd9fe1881fc004f2b34920e56b45bed90 Mon Sep 17 00:00:00 2001 From: Querz Date: Mon, 30 Sep 2019 17:15:24 +0200 Subject: [PATCH 7/7] throw exception when tryng to add tag of different type to non-empty untyped listtag --- src/main/java/net/querz/nbt/ListTag.java | 8 +++++++- src/test/java/net/querz/nbt/ListTagTest.java | 12 ++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/main/java/net/querz/nbt/ListTag.java b/src/main/java/net/querz/nbt/ListTag.java index dd54744c..321c53f4 100644 --- a/src/main/java/net/querz/nbt/ListTag.java +++ b/src/main/java/net/querz/nbt/ListTag.java @@ -115,10 +115,16 @@ public void add(T t) { public void add(int index, T t) { Objects.requireNonNull(t); - getValue().add(index, t); if (typeClass == null || typeClass == EndTag.class) { typeClass = t.getClass(); + } else if (typeClass != t.getClass()) { + throw new ClassCastException( + String.format("cannot add %s to ListTag<%s>", + t.getClass().getSimpleName(), + typeClass.getSimpleName())); } + getValue().add(index, t); + } public void addAll(Collection t) { diff --git a/src/test/java/net/querz/nbt/ListTagTest.java b/src/test/java/net/querz/nbt/ListTagTest.java index 40602c13..243db1c1 100644 --- a/src/test/java/net/querz/nbt/ListTagTest.java +++ b/src/test/java/net/querz/nbt/ListTagTest.java @@ -203,6 +203,18 @@ public void testCasting() { assertEquals(lb, lg); //only allow casting once from untyped list to typed list assertThrowsRuntimeException(lg::asShortTagList, ClassCastException.class); + + //adding generic Tags to an empty list + @SuppressWarnings("unchecked") + ListTag> u = (ListTag>) TagFactory.fromID(9); + assertEquals(EndTag.class, u.getTypeClass()); + assertThrowsNoRuntimeException(() -> u.add(new StringTag())); + assertEquals(StringTag.class, u.getTypeClass()); + assertThrowsRuntimeException(() -> u.add(new ByteTag()), ClassCastException.class); + assertThrowsNoRuntimeException(() -> u.add(new StringTag())); + assertEquals(2, u.size()); + + } public void testCompareTo() {