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);