Skip to content

Commit f62130f

Browse files
committed
Improve biome-coloring and add support for 1.15 format chunks with 3d biomes
1 parent 38b5811 commit f62130f

File tree

8 files changed

+324
-29
lines changed

8 files changed

+324
-29
lines changed

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,8 @@ public static Chunk create(MCAWorld world, CompoundTag chunkTag) throws IOExcept
7575
int version = chunkTag.getInt("DataVersion");
7676

7777
if (version <= 1343) return new ChunkAnvil112(world, chunkTag);
78-
return new ChunkAnvil113(world, chunkTag);
78+
if (version <= 1976) return new ChunkAnvil113(world, chunkTag);
79+
return new ChunkAnvil115(world, chunkTag);
7980
}
8081

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

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,9 +116,9 @@ public LightData getLightData(Vector3i pos) {
116116
public Biome getBiome(Vector3i pos) {
117117
int x = pos.getX() & 0xF; // Math.floorMod(pos.getX(), 16)
118118
int z = pos.getZ() & 0xF;
119-
int biomeByteIndex = z * 16 + x;
119+
int biomeIntIndex = z * 16 + x;
120120

121-
return biomeIdMapper.get(biomes[biomeByteIndex]);
121+
return biomeIdMapper.get(biomes[biomeIntIndex]);
122122
}
123123

124124
private class Section {
Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
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 ChunkAnvil115 extends Chunk {
47+
private BiomeMapper biomeIdMapper;
48+
49+
private boolean isGenerated;
50+
private Section[] sections;
51+
private int[] biomes;
52+
53+
@SuppressWarnings("unchecked")
54+
public ChunkAnvil115(MCAWorld world, CompoundTag chunkTag) {
55+
super(world, chunkTag);
56+
57+
biomeIdMapper = getWorld().getBiomeIdMapper();
58+
59+
CompoundTag levelData = chunkTag.getCompoundTag("Level");
60+
61+
String status = levelData.getString("Status");
62+
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
63+
64+
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?
65+
if (levelData.containsKey("Sections")) {
66+
for (CompoundTag sectionTag : ((ListTag<CompoundTag>) levelData.getListTag("Sections"))) {
67+
Section section = new Section(sectionTag);
68+
if (section.getSectionY() >= 0) sections[section.getSectionY()] = section;
69+
}
70+
}
71+
72+
Tag<?> tag = levelData.get("Biomes"); //tag can be byte-array or int-array
73+
if (tag instanceof ByteArrayTag) {
74+
byte[] bs = ((ByteArrayTag) tag).getValue();
75+
biomes = new int[bs.length];
76+
77+
for (int i = 0; i < bs.length; i++) {
78+
biomes[i] = bs[i] & 0xFF;
79+
}
80+
}
81+
else if (tag instanceof IntArrayTag) {
82+
biomes = ((IntArrayTag) tag).getValue();
83+
}
84+
85+
if (biomes == null || biomes.length == 0) {
86+
biomes = new int[2048];
87+
}
88+
}
89+
90+
@Override
91+
public boolean isGenerated() {
92+
return isGenerated;
93+
}
94+
95+
@Override
96+
public BlockState getBlockState(Vector3i pos) {
97+
int sectionY = MCAUtil.blockToChunk(pos.getY());
98+
99+
Section section = this.sections[sectionY];
100+
if (section == null) return BlockState.AIR;
101+
102+
return section.getBlockState(pos);
103+
}
104+
105+
@Override
106+
public LightData getLightData(Vector3i pos) {
107+
int sectionY = MCAUtil.blockToChunk(pos.getY());
108+
109+
Section section = this.sections[sectionY];
110+
if (section == null) return LightData.FULL;
111+
112+
return section.getLightData(pos);
113+
}
114+
115+
@Override
116+
public Biome getBiome(Vector3i pos) {
117+
int x = (pos.getX() & 0xF) / 4; // Math.floorMod(pos.getX(), 16)
118+
int z = (pos.getZ() & 0xF) / 4;
119+
int y = pos.getY() / 4;
120+
int biomeIntIndex = y * 16 + z * 4 + x;
121+
122+
return biomeIdMapper.get(biomes[biomeIntIndex]);
123+
}
124+
125+
private class Section {
126+
private static final String AIR_ID = "minecraft:air";
127+
128+
private int sectionY;
129+
private byte[] blockLight;
130+
private byte[] skyLight;
131+
private long[] blocks;
132+
private BlockState[] palette;
133+
134+
@SuppressWarnings("unchecked")
135+
public Section(CompoundTag sectionData) {
136+
this.sectionY = sectionData.getByte("Y");
137+
this.blockLight = sectionData.getByteArray("BlockLight");
138+
if (blockLight.length == 0) blockLight = new byte[2048];
139+
this.skyLight = sectionData.getByteArray("SkyLight");
140+
if (skyLight.length == 0) skyLight = new byte[2048];
141+
this.blocks = sectionData.getLongArray("BlockStates");
142+
143+
//read block palette
144+
ListTag<CompoundTag> paletteTag = (ListTag<CompoundTag>) sectionData.getListTag("Palette");
145+
if (paletteTag != null) {
146+
this.palette = new BlockState[paletteTag.size()];
147+
for (int i = 0; i < this.palette.length; i++) {
148+
CompoundTag stateTag = paletteTag.get(i);
149+
150+
String id = stateTag.getString("Name"); //shortcut to save time and memory
151+
if (id.equals(AIR_ID)) {
152+
palette[i] = BlockState.AIR;
153+
continue;
154+
}
155+
156+
Map<String, String> properties = new HashMap<>();
157+
158+
if (stateTag.containsKey("Properties")) {
159+
CompoundTag propertiesTag = stateTag.getCompoundTag("Properties");
160+
for (Entry<String, Tag<?>> property : propertiesTag) {
161+
properties.put(property.getKey().toLowerCase(), ((StringTag) property.getValue()).getValue().toLowerCase());
162+
}
163+
}
164+
165+
palette[i] = new BlockState(id, properties);
166+
}
167+
} else {
168+
this.palette = new BlockState[0];
169+
}
170+
}
171+
172+
public int getSectionY() {
173+
return sectionY;
174+
}
175+
176+
public BlockState getBlockState(Vector3i pos) {
177+
if (blocks.length == 0) return BlockState.AIR;
178+
179+
int x = pos.getX() & 0xF; // Math.floorMod(pos.getX(), 16)
180+
int y = pos.getY() & 0xF;
181+
int z = pos.getZ() & 0xF;
182+
int blockIndex = y * 256 + z * 16 + x;
183+
int bitsPerBlock = blocks.length * 64 / 4096; //64 bits per long and 4096 blocks per section
184+
int index = blockIndex * bitsPerBlock;
185+
int firstLong = index >> 6; // index / 64
186+
int bitoffset = index & 0x3F; // Math.floorMod(index, 64)
187+
188+
long value = blocks[firstLong] >>> bitoffset;
189+
190+
if (bitoffset > 0 && firstLong + 1 < blocks.length) {
191+
long value2 = blocks[firstLong + 1];
192+
value2 = value2 << -bitoffset;
193+
value = value | value2;
194+
}
195+
196+
value = value & (0xFFFFFFFFFFFFFFFFL >>> -bitsPerBlock);
197+
198+
if (value >= palette.length) {
199+
Logger.global.noFloodWarning("palettewarning", "Got palette value " + value + " but palette has size of " + palette.length + " (Future occasions of this error will not be logged)");
200+
return BlockState.MISSING;
201+
}
202+
203+
return palette[(int) value];
204+
}
205+
206+
public LightData getLightData(Vector3i pos) {
207+
int x = pos.getX() & 0xF; // Math.floorMod(pos.getX(), 16)
208+
int y = pos.getY() & 0xF;
209+
int z = pos.getZ() & 0xF;
210+
int blockByteIndex = y * 256 + z * 16 + x;
211+
int blockHalfByteIndex = blockByteIndex >> 1; // blockByteIndex / 2
212+
boolean largeHalf = (blockByteIndex & 0x1) != 0; // (blockByteIndex % 2) == 0
213+
214+
int blockLight = getByteHalf(this.blockLight[blockHalfByteIndex], largeHalf);
215+
int skyLight = getByteHalf(this.skyLight[blockHalfByteIndex], largeHalf);
216+
217+
return new LightData(skyLight, blockLight);
218+
}
219+
220+
/**
221+
* 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>
222+
* The value is treated as an unsigned byte.
223+
*/
224+
private int getByteHalf(int value, boolean largeHalf) {
225+
value = value & 0xFF;
226+
if (largeHalf) {
227+
value = value >> 4;
228+
}
229+
value = value & 0xF;
230+
return value;
231+
}
232+
}
233+
234+
}

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,19 @@ public BlockState getBlockState(Vector3i pos) {
149149
}
150150
}
151151

152+
@Override
153+
public Biome getBiome(Vector3i pos) {
154+
try {
155+
156+
Vector2i chunkPos = blockToChunk(pos);
157+
Chunk chunk = getChunk(chunkPos);
158+
return chunk.getBiome(pos);
159+
160+
} catch (IOException ex) {
161+
throw new RuntimeException("Unexpected IO-Exception trying to read world-data!", ex);
162+
}
163+
}
164+
152165
@Override
153166
public Block getBlock(Vector3i pos) {
154167
if (pos.getY() < getMinY()) {

0 commit comments

Comments
 (0)