Skip to content

Commit

Permalink
Merge pull request #373 from mircokroon/chunk-fix
Browse files Browse the repository at this point in the history
Fixed issues with savings chunks in some situations
  • Loading branch information
mircokroon authored Jun 7, 2022
2 parents bb1479a + c307eac commit 072c07f
Show file tree
Hide file tree
Showing 12 changed files with 249 additions and 31 deletions.
5 changes: 4 additions & 1 deletion src/main/java/game/data/chunk/BlockEntityRegistry.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ public boolean isBlockEntity(String id) {

// should probably name this something else
public boolean isSpecialBlockEntity(String id) {
return id.endsWith("shulker_box") || id.endsWith("_bed") || id.endsWith("command_block");
return id.endsWith("shulker_box")
|| id.endsWith("_bed")
|| id.endsWith("command_block")
|| id.endsWith("banner");
}
}
3 changes: 2 additions & 1 deletion src/main/java/game/data/chunk/Chunk.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import game.data.chunk.palette.BlockState;
import game.data.chunk.palette.GlobalPaletteProvider;
import game.data.chunk.palette.Palette;
import game.data.chunk.palette.PaletteType;
import game.data.coordinates.Coordinate2D;
import game.data.coordinates.Coordinate3D;
import game.data.coordinates.CoordinateDim2D;
Expand Down Expand Up @@ -129,7 +130,7 @@ public void readChunkColumn(boolean full, BitSet mask, DataTypeProvider dataProv
readBlockCount(dataProvider);

byte bitsPerBlock = dataProvider.readNext();
Palette palette = Palette.readPalette(bitsPerBlock, dataProvider);
Palette palette = Palette.readPalette(bitsPerBlock, dataProvider, PaletteType.BLOCKS);

// A bitmask that contains bitsPerBlock set bits
int dataArrayLength = dataProvider.readVarInt();
Expand Down
5 changes: 5 additions & 0 deletions src/main/java/game/data/chunk/ChunkEntities.java
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@ protected void addBlockEntity(SpecificTag nbtTag) {
protected CompoundTag generateBlockEntity(String id, Coordinate3D containerLocation) {
String entId = id;

// TODO: make a list of these
// all shulker colours have the same block entity
if (id.endsWith("shulker_box")) {
entId = "minecraft:shulker_box";
Expand All @@ -214,6 +215,10 @@ protected CompoundTag generateBlockEntity(String id, Coordinate3D containerLocat
if (id.endsWith("command_block")) {
entId = "minecraft:command_block";
}
// Covers banners
if (id.endsWith("banner")) {
entId = "minecraft:banner";
}

CompoundTag entity = new CompoundTag();
entity.add("id", new StringTag(entId));
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/game/data/chunk/palette/DirectPalette.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import java.util.List;

public class DirectPalette extends Palette {
int bitsPerBlock;
private final int bitsPerBlock;

public DirectPalette() {
this(GlobalPaletteProvider.getGlobalPalette().getRequiredBits());
Expand Down
45 changes: 34 additions & 11 deletions src/main/java/game/data/chunk/palette/Palette.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package game.data.chunk.palette;

import game.data.chunk.version.ChunkSection_1_18;
import game.data.chunk.version.encoder.BlockLocationEncoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
Expand All @@ -15,9 +17,10 @@
* Class to hold a palette of a chunk.
*/
public class Palette {
protected int bitsPerBlock;
private int bitsPerBlock;
private int[] palette;
StateProvider stateProvider;
PaletteType type = PaletteType.BLOCKS;

protected Palette() {
// palette needs initializing
Expand All @@ -34,7 +37,7 @@ private Palette(int bitsPerBlock, int[] palette) {

Palette(int[] arr) {
this.palette = arr;
this.bitsPerBlock = computeBitsPerBlock(arr.length - 1);
this.bitsPerBlock = computeBitsPerBlock(Math.min(0, arr.length - 1));
this.stateProvider = GlobalPaletteProvider.getGlobalPalette();
}

Expand Down Expand Up @@ -82,29 +85,39 @@ public Palette(int dataVersion, ListTag nbt) {
}
}

protected void recomputeBitsPerBlock() {
this.bitsPerBlock = computeBitsPerBlock(this.palette.length - 1);
}

private int computeBitsPerBlock(int maxIndex) {
if (maxIndex < 0) {
maxIndex = 0;
}

int bitsNeeded = Integer.SIZE - Integer.numberOfLeadingZeros(maxIndex);
return Math.max(4, bitsNeeded);
return Math.max(type.getMinBitsPerBlock(), bitsNeeded);
}

/**
* Read the palette from the network stream.
* @param dataTypeProvider network stream reader
*/
public static Palette readPalette(DataTypeProvider dataTypeProvider) {
public static Palette readPalette(DataTypeProvider dataTypeProvider, PaletteType type) {
byte bitsPerBlock = dataTypeProvider.readNext();
return readPalette(bitsPerBlock, dataTypeProvider);
Palette palette = readPalette(bitsPerBlock, dataTypeProvider, type);
palette.type = type;
return palette;
}

/**
* Read the palette from the network stream.
* @param bitsPerBlock the number of bits per block that is used, indicates the palette type
* @param dataTypeProvider network stream reader
*/
public static Palette readPalette(int bitsPerBlock, DataTypeProvider dataTypeProvider) {
public static Palette readPalette(int bitsPerBlock, DataTypeProvider dataTypeProvider, PaletteType type) {
if (bitsPerBlock == 0) {
return new SingleValuePalette(dataTypeProvider.readVarInt());
} else if (bitsPerBlock > 8) {
} else if (bitsPerBlock > type.getMaxBitsPerBlock()) {
return new DirectPalette(bitsPerBlock);
}
int size = dataTypeProvider.readVarInt();
Expand Down Expand Up @@ -190,7 +203,6 @@ public String toString() {
'}';
}


public int getIndexFor(ChunkSection section, int blockStateId) {
for (int i = 0; i < palette.length; i++) {
if (palette[i] == blockStateId) {
Expand All @@ -203,13 +215,24 @@ public int getIndexFor(ChunkSection section, int blockStateId) {
newPalette[palette.length] = blockStateId;
this.palette = newPalette;

if (section != null) {
resize(section, bitsPerBlock);
}


return palette.length - 1;
}

public int[] getPalette() {
return palette;
}

private void resize(ChunkSection section, int oldBpp) {
int newBitsPerBlock = computeBitsPerBlock(palette.length - 1);
if (bitsPerBlock != newBitsPerBlock) {
if (oldBpp != newBitsPerBlock) {
section.resizeBlocksIfRequired(newBitsPerBlock);
this.bitsPerBlock = newBitsPerBlock;
}

return palette.length - 1;
}


Expand Down
61 changes: 61 additions & 0 deletions src/main/java/game/data/chunk/palette/PaletteTransformer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package game.data.chunk.palette;

import game.data.chunk.version.ChunkSection_1_16;
import game.data.chunk.version.encoder.BlockLocationEncoder;
import java.util.Arrays;

/**
* Transforms a direct palette to a real palette.
*/
public class PaletteTransformer {
BlockLocationEncoder locationEncoder;
Palette oldPalette;
Palette newPalette;

public PaletteTransformer(BlockLocationEncoder locationEncoder, DirectPalette oldPalette) {
this.locationEncoder = locationEncoder;
this.oldPalette = oldPalette;
this.newPalette = new Palette(new int[] { });

if (oldPalette.type == PaletteType.BIOMES) {
newPalette.biomePalette();
}
}

public long[] transform(long[] data) {
if (data.length == 0) {
return data;
}

// first add every block in the chunk to the new palette
for (int z = 0; z < 16; z++) {
for (int x = 0; x < 16; x++) {
for (int y = 0; y < 16; y++) {
locationEncoder.setTo(x, y, z, oldPalette.getBitsPerBlock());
newPalette.getIndexFor(null, locationEncoder.fetch(data));
}
}
}

newPalette.recomputeBitsPerBlock();

// copy all blocks to the new palette
long[] newData = new long[ChunkSection_1_16.longsRequired(newPalette.getBitsPerBlock())];
for (int z = 0; z < 16; z++) {
for (int x = 0; x < 16; x++) {
for (int y = 0; y < 16; y++) {
locationEncoder.setTo(x, y, z, oldPalette.getBitsPerBlock());
int index = newPalette.getIndexFor(null, locationEncoder.fetch(data));

locationEncoder.setTo(x, y, z, newPalette.getBitsPerBlock());
locationEncoder.write(newData, index);
}
}
}
return newData;
}

public Palette getNewPalette() {
return newPalette;
}
}
21 changes: 21 additions & 0 deletions src/main/java/game/data/chunk/palette/PaletteType.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package game.data.chunk.palette;

public enum PaletteType {
BLOCKS(4, 8),
BIOMES(1, 3);

private final int minBitsPerBlock, maxBitsPerBlock;

PaletteType(int minBitsPerBlock, int maxBitsPerBlock) {
this.minBitsPerBlock = minBitsPerBlock;
this.maxBitsPerBlock = maxBitsPerBlock;
}

public int getMinBitsPerBlock() {
return minBitsPerBlock;
}

public int getMaxBitsPerBlock() {
return maxBitsPerBlock;
}
}
16 changes: 14 additions & 2 deletions src/main/java/game/data/chunk/version/ChunkSection_1_16.java
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,21 @@ public synchronized void resizeBlocksIfRequired(int newBitsPerBlock) {
copyBlocks(new long[newSize], newBitsPerBlock);
}

private static int longsRequired(int bitsPerBlock) {
public static int longsRequired(int bitsPerBlock) {
return longsRequired(bitsPerBlock, 4096);
}

public static int longsRequiredBiomes(int bitsPerBlock) {
return longsRequired(bitsPerBlock, 64);
}

private static int longsRequired(int bitsPerBlock, double totalItems) {
if (bitsPerBlock == 0) {
return 0;
}

int blocksPerLong = 64 / bitsPerBlock;
return (int) Math.ceil(4096.0 / blocksPerLong);
return (int) Math.ceil(totalItems / blocksPerLong);
}
}

37 changes: 29 additions & 8 deletions src/main/java/game/data/chunk/version/ChunkSection_1_18.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@

import config.Version;
import game.data.chunk.Chunk;
import game.data.chunk.palette.DirectPalette;
import game.data.chunk.palette.Palette;
import game.data.chunk.palette.PaletteTransformer;
import game.data.chunk.palette.PaletteType;
import game.data.chunk.palette.SingleValuePalette;
import game.data.chunk.version.encoder.BlockLocationEncoder_1_16;
import game.data.coordinates.Coordinate3D;
import se.llbit.nbt.*;

Expand Down Expand Up @@ -41,8 +43,8 @@ public void setBiomePalette(Palette biomePalette) {
public CompoundTag toNbt() {
CompoundTag tag = new CompoundTag();

tag.add("biomes", getPalettedCompound(biomePalette, Tag.TAG_STRING, biomes));
tag.add("block_states", getPalettedCompound(palette, Tag.TAG_COMPOUND, blocks));
tag.add("biomes", getPalettedCompound(biomePalette, Tag.TAG_STRING, biomes, PaletteType.BIOMES));
tag.add("block_states", getPalettedCompound(palette, Tag.TAG_COMPOUND, blocks, PaletteType.BLOCKS));

tag.add("Y", new ByteTag(y));
if (blockLight != null && blockLight.length > 0) {
Expand All @@ -55,16 +57,35 @@ public CompoundTag toNbt() {
return tag;
}

private CompoundTag getPalettedCompound(Palette palette, int tagType, long[] data) {
private CompoundTag getPalettedCompound(Palette palette, int tagType, long[] data, PaletteType type) {
CompoundTag tag = new CompoundTag();

// should this even happen?
if (palette == null) {
return tag;
// If the palette is empty (usually meaning no blocks in a section), set it to a palette
// with just air in it.
if (palette == null || data.length == 0) {
palette = new SingleValuePalette(0);
if (type == PaletteType.BIOMES) {
palette.biomePalette();
}
}

// If we have a direct palette, we need to convert it to a proper palette since the world
// format doesn't allow direct palettes (I think).
if (palette instanceof DirectPalette directPalette) {
PaletteTransformer transformer = new PaletteTransformer(getLocationEncoder(), directPalette);
long[] newData = transformer.transform(data);

if (newData != data) {
data = newData;
palette = transformer.getNewPalette();
}
}

List<SpecificTag> paletteItems = palette.toNbt();
if (!paletteItems.isEmpty()) {
if (paletteItems.isEmpty()) {
// this shouldn't ever happen
System.err.println("Empty palette @ " + getY() + " :: " + palette);
} else {
tag.add("palette", new ListTag(tagType, paletteItems));
}
if (data != null && data.length > 0) {
Expand Down
15 changes: 11 additions & 4 deletions src/main/java/game/data/chunk/version/Chunk_1_18.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import config.Version;
import game.data.chunk.BlockEntityRegistry;
import game.data.chunk.palette.GlobalPalette;
import game.data.chunk.palette.PaletteType;
import game.data.chunk.version.encoder.BlockLocationEncoder_1_16;
import game.data.registries.RegistryManager;
import game.data.chunk.ChunkSection;
import game.data.chunk.palette.BlockState;
Expand Down Expand Up @@ -67,20 +69,25 @@ protected void parse(DataTypeProvider dataProvider) {
*/
public void readChunkColumn(DataTypeProvider dataProvider) {
// Loop through section Y values, starting from the lowest section that has blocks inside it.
for (int sectionY = getMinBlockSection(); sectionY <= getMaxSection() + 1 && dataProvider.hasNext(); sectionY++) {
for (int sectionY = getMinBlockSection(); sectionY <= getMaxSection() && dataProvider.hasNext(); sectionY++) {
ChunkSection_1_18 section = (ChunkSection_1_18) getChunkSection(sectionY);

int blockCount = dataProvider.readShort();
Palette blockPalette = Palette.readPalette(dataProvider);
Palette blockPalette = Palette.readPalette(dataProvider, PaletteType.BLOCKS);

if (section == null) {
section = (ChunkSection_1_18) createNewChunkSection((byte) (sectionY & 0xFF), blockPalette);
}

section.setBlocks(dataProvider.readLongArray(dataProvider.readVarInt()));

section.setBiomePalette(Palette.readPalette(dataProvider));
section.setBiomes(dataProvider.readLongArray(dataProvider.readVarInt()));
// biomes
Palette biomePalette = Palette.readPalette(dataProvider, PaletteType.BIOMES);
section.setBiomePalette(biomePalette);

// check how many longs we expect, if there's more discard the rest
int longsExpectedBiomes = ChunkSection_1_18.longsRequiredBiomes(biomePalette.getBitsPerBlock());
section.setBiomes(dataProvider.readLongArray(dataProvider.readVarInt(), longsExpectedBiomes));

// May replace an existing section or a null one
setChunkSection(sectionY, section);
Expand Down
Loading

0 comments on commit 072c07f

Please sign in to comment.