Skip to content

Commit 67cb42f

Browse files
committed
Add support for reading 1.16 region-files
1 parent 54364c4 commit 67cb42f

File tree

3 files changed

+258
-2
lines changed

3 files changed

+258
-2
lines changed

BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/Chunk.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,13 @@ public abstract class Chunk {
3939
private final MCAWorld world;
4040
private final Vector2i chunkPos;
4141

42+
private final int dataVersion;
43+
4244
protected Chunk(MCAWorld world, Vector2i chunkPos) {
4345
this.world = world;
4446
this.chunkPos = chunkPos;
47+
48+
this.dataVersion = -1;
4549
}
4650

4751
protected Chunk(MCAWorld world, CompoundTag chunkTag) {
@@ -53,6 +57,8 @@ protected Chunk(MCAWorld world, CompoundTag chunkTag) {
5357
levelData.getInt("xPos"),
5458
levelData.getInt("zPos")
5559
);
60+
61+
dataVersion = chunkTag.getInt("DataVersion");
5662
}
5763

5864
public abstract boolean isGenerated();
@@ -65,6 +71,10 @@ public MCAWorld getWorld() {
6571
return world;
6672
}
6773

74+
public int getDataVersion() {
75+
return dataVersion;
76+
}
77+
6878
public abstract BlockState getBlockState(Vector3i pos);
6979

7080
public abstract LightData getLightData(Vector3i pos);
@@ -76,7 +86,8 @@ public static Chunk create(MCAWorld world, CompoundTag chunkTag, boolean ignoreM
7686

7787
if (version <= 1343) return new ChunkAnvil112(world, chunkTag, ignoreMissingLightData);
7888
if (version <= 1976) return new ChunkAnvil113(world, chunkTag, ignoreMissingLightData);
79-
return new ChunkAnvil115(world, chunkTag, ignoreMissingLightData);
89+
if (version < 2534) return new ChunkAnvil115(world, chunkTag, ignoreMissingLightData);
90+
return new ChunkAnvil116(world, chunkTag, ignoreMissingLightData);
8091
}
8192

8293
public static Chunk empty(MCAWorld world, Vector2i chunkPos) {

BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/ChunkAnvil115.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,8 @@ private class Section {
139139
private long[] blocks;
140140
private BlockState[] palette;
141141

142+
private int bitsPerBlock;
143+
142144
@SuppressWarnings("unchecked")
143145
public Section(CompoundTag sectionData) {
144146
this.sectionY = sectionData.getByte("Y");
@@ -175,6 +177,8 @@ public Section(CompoundTag sectionData) {
175177
} else {
176178
this.palette = new BlockState[0];
177179
}
180+
181+
this.bitsPerBlock = blocks.length * 64 / 4096; //64 bits per long and 4096 blocks per section
178182
}
179183

180184
public int getSectionY() {
@@ -188,7 +192,6 @@ public BlockState getBlockState(Vector3i pos) {
188192
int y = pos.getY() & 0xF;
189193
int z = pos.getZ() & 0xF;
190194
int blockIndex = y * 256 + z * 16 + x;
191-
int bitsPerBlock = blocks.length * 64 / 4096; //64 bits per long and 4096 blocks per section
192195
int index = blockIndex * bitsPerBlock;
193196
int firstLong = index >> 6; // index / 64
194197
int bitoffset = index & 0x3F; // Math.floorMod(index, 64)
Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
/*
2+
* This file is part of BlueMap, licensed under the MIT License (MIT).
3+
*
4+
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
5+
* Copyright (c) contributors
6+
*
7+
* Permission is hereby granted, free of charge, to any person obtaining a copy
8+
* of this software and associated documentation files (the "Software"), to deal
9+
* in the Software without restriction, including without limitation the rights
10+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11+
* copies of the Software, and to permit persons to whom the Software is
12+
* furnished to do so, subject to the following conditions:
13+
*
14+
* The above copyright notice and this permission notice shall be included in
15+
* all copies or substantial portions of the Software.
16+
*
17+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23+
* THE SOFTWARE.
24+
*/
25+
package de.bluecolored.bluemap.core.mca;
26+
27+
import java.util.HashMap;
28+
import java.util.Map;
29+
import java.util.Map.Entry;
30+
31+
import com.flowpowered.math.vector.Vector3i;
32+
33+
import de.bluecolored.bluemap.core.logger.Logger;
34+
import de.bluecolored.bluemap.core.mca.mapping.BiomeMapper;
35+
import de.bluecolored.bluemap.core.world.Biome;
36+
import de.bluecolored.bluemap.core.world.BlockState;
37+
import de.bluecolored.bluemap.core.world.LightData;
38+
import net.querz.nbt.ByteArrayTag;
39+
import net.querz.nbt.CompoundTag;
40+
import net.querz.nbt.IntArrayTag;
41+
import net.querz.nbt.ListTag;
42+
import net.querz.nbt.StringTag;
43+
import net.querz.nbt.Tag;
44+
import net.querz.nbt.mca.MCAUtil;
45+
46+
public class ChunkAnvil116 extends Chunk {
47+
private BiomeMapper biomeIdMapper;
48+
49+
private boolean isGenerated;
50+
private boolean hasLight;
51+
private Section[] sections;
52+
private int[] biomes;
53+
54+
@SuppressWarnings("unchecked")
55+
public ChunkAnvil116(MCAWorld world, CompoundTag chunkTag, boolean ignoreMissingLightData) {
56+
super(world, chunkTag);
57+
58+
biomeIdMapper = getWorld().getBiomeIdMapper();
59+
60+
CompoundTag levelData = chunkTag.getCompoundTag("Level");
61+
62+
String status = levelData.getString("Status");
63+
isGenerated = status.equals("full") || status.equals("spawn"); // full is normal fully generated and spawn seems to be converted from old format but not yet loaded if you optimized your world
64+
hasLight = isGenerated;
65+
66+
if (!isGenerated && ignoreMissingLightData) {
67+
isGenerated = !status.equals("empty");
68+
}
69+
70+
sections = new Section[32]; //32 supports a max world-height of 512 which is the max that the hightmaps of Minecraft V1.13+ can store with 9 bits, i believe?
71+
if (levelData.containsKey("Sections")) {
72+
for (CompoundTag sectionTag : ((ListTag<CompoundTag>) levelData.getListTag("Sections"))) {
73+
Section section = new Section(sectionTag);
74+
if (section.getSectionY() >= 0) sections[section.getSectionY()] = section;
75+
}
76+
}
77+
78+
Tag<?> tag = levelData.get("Biomes"); //tag can be byte-array or int-array
79+
if (tag instanceof ByteArrayTag) {
80+
byte[] bs = ((ByteArrayTag) tag).getValue();
81+
biomes = new int[bs.length];
82+
83+
for (int i = 0; i < bs.length; i++) {
84+
biomes[i] = bs[i] & 0xFF;
85+
}
86+
}
87+
else if (tag instanceof IntArrayTag) {
88+
biomes = ((IntArrayTag) tag).getValue();
89+
}
90+
91+
if (biomes == null || biomes.length == 0) {
92+
biomes = new int[2048];
93+
}
94+
}
95+
96+
@Override
97+
public boolean isGenerated() {
98+
return isGenerated;
99+
}
100+
101+
@Override
102+
public BlockState getBlockState(Vector3i pos) {
103+
int sectionY = MCAUtil.blockToChunk(pos.getY());
104+
105+
Section section = this.sections[sectionY];
106+
if (section == null) return BlockState.AIR;
107+
108+
return section.getBlockState(pos);
109+
}
110+
111+
@Override
112+
public LightData getLightData(Vector3i pos) {
113+
if (!hasLight) return LightData.SKY;
114+
115+
int sectionY = MCAUtil.blockToChunk(pos.getY());
116+
117+
Section section = this.sections[sectionY];
118+
if (section == null) return LightData.SKY;
119+
120+
return section.getLightData(pos);
121+
}
122+
123+
@Override
124+
public Biome getBiome(Vector3i pos) {
125+
int x = (pos.getX() & 0xF) / 4; // Math.floorMod(pos.getX(), 16)
126+
int z = (pos.getZ() & 0xF) / 4;
127+
int y = pos.getY() / 4;
128+
int biomeIntIndex = y * 16 + z * 4 + x;
129+
130+
return biomeIdMapper.get(biomes[biomeIntIndex]);
131+
}
132+
133+
private class Section {
134+
private static final String AIR_ID = "minecraft:air";
135+
136+
private int sectionY;
137+
private byte[] blockLight;
138+
private byte[] skyLight;
139+
private long[] blocks;
140+
private BlockState[] palette;
141+
142+
private int bitsPerBlock;
143+
private int blocksPerLong;
144+
145+
@SuppressWarnings("unchecked")
146+
public Section(CompoundTag sectionData) {
147+
this.sectionY = sectionData.getByte("Y");
148+
this.blockLight = sectionData.getByteArray("BlockLight");
149+
if (blockLight.length == 0) blockLight = new byte[2048];
150+
this.skyLight = sectionData.getByteArray("SkyLight");
151+
if (skyLight.length == 0) skyLight = new byte[2048];
152+
this.blocks = sectionData.getLongArray("BlockStates");
153+
154+
//read block palette
155+
ListTag<CompoundTag> paletteTag = (ListTag<CompoundTag>) sectionData.getListTag("Palette");
156+
if (paletteTag != null) {
157+
this.palette = new BlockState[paletteTag.size()];
158+
for (int i = 0; i < this.palette.length; i++) {
159+
CompoundTag stateTag = paletteTag.get(i);
160+
161+
String id = stateTag.getString("Name"); //shortcut to save time and memory
162+
if (id.equals(AIR_ID)) {
163+
palette[i] = BlockState.AIR;
164+
continue;
165+
}
166+
167+
Map<String, String> properties = new HashMap<>();
168+
169+
if (stateTag.containsKey("Properties")) {
170+
CompoundTag propertiesTag = stateTag.getCompoundTag("Properties");
171+
for (Entry<String, Tag<?>> property : propertiesTag) {
172+
properties.put(property.getKey().toLowerCase(), ((StringTag) property.getValue()).getValue().toLowerCase());
173+
}
174+
}
175+
176+
palette[i] = new BlockState(id, properties);
177+
}
178+
} else {
179+
this.palette = new BlockState[0];
180+
}
181+
182+
183+
this.bitsPerBlock = 32 - Integer.numberOfLeadingZeros(palette.length - 1);
184+
if (this.bitsPerBlock < 4) this.bitsPerBlock = 4;
185+
this.blocksPerLong = 64 / bitsPerBlock;
186+
}
187+
188+
public int getSectionY() {
189+
return sectionY;
190+
}
191+
192+
public BlockState getBlockState(Vector3i pos) {
193+
if (blocks.length == 0) return BlockState.AIR;
194+
195+
int x = pos.getX() & 0xF; // Math.floorMod(pos.getX(), 16)
196+
int y = pos.getY() & 0xF;
197+
int z = pos.getZ() & 0xF;
198+
int blockIndex = y * 256 + z * 16 + x;
199+
int longIndex = blockIndex / blocksPerLong;
200+
int bitIndex = (blockIndex % blocksPerLong) * bitsPerBlock;
201+
202+
long value = blocks[longIndex] >>> bitIndex;
203+
204+
value = value & (0xFFFFFFFFFFFFFFFFL >>> -bitsPerBlock);
205+
206+
if (value >= palette.length) {
207+
Logger.global.noFloodWarning("palettewarning", "Got palette value " + value + " but palette has size of " + palette.length + "! (Future occasions of this error will not be logged)");
208+
return BlockState.MISSING;
209+
}
210+
211+
return palette[(int) value];
212+
}
213+
214+
public LightData getLightData(Vector3i pos) {
215+
int x = pos.getX() & 0xF; // Math.floorMod(pos.getX(), 16)
216+
int y = pos.getY() & 0xF;
217+
int z = pos.getZ() & 0xF;
218+
int blockByteIndex = y * 256 + z * 16 + x;
219+
int blockHalfByteIndex = blockByteIndex >> 1; // blockByteIndex / 2
220+
boolean largeHalf = (blockByteIndex & 0x1) != 0; // (blockByteIndex % 2) == 0
221+
222+
int blockLight = getByteHalf(this.blockLight[blockHalfByteIndex], largeHalf);
223+
int skyLight = getByteHalf(this.skyLight[blockHalfByteIndex], largeHalf);
224+
225+
return new LightData(skyLight, blockLight);
226+
}
227+
228+
/**
229+
* Extracts the 4 bits of the left (largeHalf = <code>true</code>) or the right (largeHalf = <code>false</code>) side of the byte stored in <code>value</code>.<br>
230+
* The value is treated as an unsigned byte.
231+
*/
232+
private int getByteHalf(int value, boolean largeHalf) {
233+
value = value & 0xFF;
234+
if (largeHalf) {
235+
value = value >> 4;
236+
}
237+
value = value & 0xF;
238+
return value;
239+
}
240+
}
241+
242+
}

0 commit comments

Comments
 (0)