diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/Chunk.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/Chunk.java index 736dac18..89272a17 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/Chunk.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/Chunk.java @@ -75,7 +75,8 @@ public static Chunk create(MCAWorld world, CompoundTag chunkTag) throws IOExcept int version = chunkTag.getInt("DataVersion"); if (version <= 1343) return new ChunkAnvil112(world, chunkTag); - return new ChunkAnvil113(world, chunkTag); + if (version <= 1976) return new ChunkAnvil113(world, chunkTag); + return new ChunkAnvil115(world, chunkTag); } public static Chunk empty(MCAWorld world, Vector2i chunkPos) { diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/ChunkAnvil113.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/ChunkAnvil113.java index 4b93bd08..0d7cd574 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/ChunkAnvil113.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/ChunkAnvil113.java @@ -116,9 +116,9 @@ public LightData getLightData(Vector3i pos) { public Biome getBiome(Vector3i pos) { int x = pos.getX() & 0xF; // Math.floorMod(pos.getX(), 16) int z = pos.getZ() & 0xF; - int biomeByteIndex = z * 16 + x; + int biomeIntIndex = z * 16 + x; - return biomeIdMapper.get(biomes[biomeByteIndex]); + return biomeIdMapper.get(biomes[biomeIntIndex]); } private class Section { diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/ChunkAnvil115.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/ChunkAnvil115.java new file mode 100644 index 00000000..5ebff346 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/ChunkAnvil115.java @@ -0,0 +1,234 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package de.bluecolored.bluemap.core.mca; + +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; + +import com.flowpowered.math.vector.Vector3i; + +import de.bluecolored.bluemap.core.logger.Logger; +import de.bluecolored.bluemap.core.mca.mapping.BiomeMapper; +import de.bluecolored.bluemap.core.world.Biome; +import de.bluecolored.bluemap.core.world.BlockState; +import de.bluecolored.bluemap.core.world.LightData; +import net.querz.nbt.ByteArrayTag; +import net.querz.nbt.CompoundTag; +import net.querz.nbt.IntArrayTag; +import net.querz.nbt.ListTag; +import net.querz.nbt.StringTag; +import net.querz.nbt.Tag; +import net.querz.nbt.mca.MCAUtil; + +public class ChunkAnvil115 extends Chunk { + private BiomeMapper biomeIdMapper; + + private boolean isGenerated; + private Section[] sections; + private int[] biomes; + + @SuppressWarnings("unchecked") + public ChunkAnvil115(MCAWorld world, CompoundTag chunkTag) { + super(world, chunkTag); + + biomeIdMapper = getWorld().getBiomeIdMapper(); + + CompoundTag levelData = chunkTag.getCompoundTag("Level"); + + String status = levelData.getString("Status"); + 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 + + 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? + if (levelData.containsKey("Sections")) { + for (CompoundTag sectionTag : ((ListTag) levelData.getListTag("Sections"))) { + Section section = new Section(sectionTag); + if (section.getSectionY() >= 0) sections[section.getSectionY()] = section; + } + } + + Tag tag = levelData.get("Biomes"); //tag can be byte-array or int-array + if (tag instanceof ByteArrayTag) { + byte[] bs = ((ByteArrayTag) tag).getValue(); + biomes = new int[bs.length]; + + for (int i = 0; i < bs.length; i++) { + biomes[i] = bs[i] & 0xFF; + } + } + else if (tag instanceof IntArrayTag) { + biomes = ((IntArrayTag) tag).getValue(); + } + + if (biomes == null || biomes.length == 0) { + biomes = new int[2048]; + } + } + + @Override + public boolean isGenerated() { + return isGenerated; + } + + @Override + public BlockState getBlockState(Vector3i pos) { + int sectionY = MCAUtil.blockToChunk(pos.getY()); + + Section section = this.sections[sectionY]; + if (section == null) return BlockState.AIR; + + return section.getBlockState(pos); + } + + @Override + public LightData getLightData(Vector3i pos) { + int sectionY = MCAUtil.blockToChunk(pos.getY()); + + Section section = this.sections[sectionY]; + if (section == null) return LightData.FULL; + + return section.getLightData(pos); + } + + @Override + public Biome getBiome(Vector3i pos) { + int x = (pos.getX() & 0xF) / 4; // Math.floorMod(pos.getX(), 16) + int z = (pos.getZ() & 0xF) / 4; + int y = pos.getY() / 4; + int biomeIntIndex = y * 16 + z * 4 + x; + + return biomeIdMapper.get(biomes[biomeIntIndex]); + } + + private class Section { + private static final String AIR_ID = "minecraft:air"; + + private int sectionY; + private byte[] blockLight; + private byte[] skyLight; + private long[] blocks; + private BlockState[] palette; + + @SuppressWarnings("unchecked") + public Section(CompoundTag sectionData) { + this.sectionY = sectionData.getByte("Y"); + this.blockLight = sectionData.getByteArray("BlockLight"); + if (blockLight.length == 0) blockLight = new byte[2048]; + this.skyLight = sectionData.getByteArray("SkyLight"); + if (skyLight.length == 0) skyLight = new byte[2048]; + this.blocks = sectionData.getLongArray("BlockStates"); + + //read block palette + ListTag paletteTag = (ListTag) sectionData.getListTag("Palette"); + if (paletteTag != null) { + this.palette = new BlockState[paletteTag.size()]; + for (int i = 0; i < this.palette.length; i++) { + CompoundTag stateTag = paletteTag.get(i); + + String id = stateTag.getString("Name"); //shortcut to save time and memory + if (id.equals(AIR_ID)) { + palette[i] = BlockState.AIR; + continue; + } + + Map properties = new HashMap<>(); + + if (stateTag.containsKey("Properties")) { + CompoundTag propertiesTag = stateTag.getCompoundTag("Properties"); + for (Entry> property : propertiesTag) { + properties.put(property.getKey().toLowerCase(), ((StringTag) property.getValue()).getValue().toLowerCase()); + } + } + + palette[i] = new BlockState(id, properties); + } + } else { + this.palette = new BlockState[0]; + } + } + + public int getSectionY() { + return sectionY; + } + + public BlockState getBlockState(Vector3i pos) { + if (blocks.length == 0) return BlockState.AIR; + + int x = pos.getX() & 0xF; // Math.floorMod(pos.getX(), 16) + int y = pos.getY() & 0xF; + int z = pos.getZ() & 0xF; + int blockIndex = y * 256 + z * 16 + x; + int bitsPerBlock = blocks.length * 64 / 4096; //64 bits per long and 4096 blocks per section + int index = blockIndex * bitsPerBlock; + int firstLong = index >> 6; // index / 64 + int bitoffset = index & 0x3F; // Math.floorMod(index, 64) + + long value = blocks[firstLong] >>> bitoffset; + + if (bitoffset > 0 && firstLong + 1 < blocks.length) { + long value2 = blocks[firstLong + 1]; + value2 = value2 << -bitoffset; + value = value | value2; + } + + value = value & (0xFFFFFFFFFFFFFFFFL >>> -bitsPerBlock); + + if (value >= palette.length) { + Logger.global.noFloodWarning("palettewarning", "Got palette value " + value + " but palette has size of " + palette.length + " (Future occasions of this error will not be logged)"); + return BlockState.MISSING; + } + + return palette[(int) value]; + } + + public LightData getLightData(Vector3i pos) { + int x = pos.getX() & 0xF; // Math.floorMod(pos.getX(), 16) + int y = pos.getY() & 0xF; + int z = pos.getZ() & 0xF; + int blockByteIndex = y * 256 + z * 16 + x; + int blockHalfByteIndex = blockByteIndex >> 1; // blockByteIndex / 2 + boolean largeHalf = (blockByteIndex & 0x1) != 0; // (blockByteIndex % 2) == 0 + + int blockLight = getByteHalf(this.blockLight[blockHalfByteIndex], largeHalf); + int skyLight = getByteHalf(this.skyLight[blockHalfByteIndex], largeHalf); + + return new LightData(skyLight, blockLight); + } + + /** + * Extracts the 4 bits of the left (largeHalf = true) or the right (largeHalf = false) side of the byte stored in value.
+ * The value is treated as an unsigned byte. + */ + private int getByteHalf(int value, boolean largeHalf) { + value = value & 0xFF; + if (largeHalf) { + value = value >> 4; + } + value = value & 0xF; + return value; + } + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/MCAWorld.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/MCAWorld.java index da27be0e..849e2511 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/MCAWorld.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/MCAWorld.java @@ -149,6 +149,19 @@ public BlockState getBlockState(Vector3i pos) { } } + @Override + public Biome getBiome(Vector3i pos) { + try { + + Vector2i chunkPos = blockToChunk(pos); + Chunk chunk = getChunk(chunkPos); + return chunk.getBiome(pos); + + } catch (IOException ex) { + throw new RuntimeException("Unexpected IO-Exception trying to read world-data!", ex); + } + } + @Override public Block getBlock(Vector3i pos) { if (pos.getY() < getMinY()) { diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/BlockColorCalculator.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/BlockColorCalculator.java index c93654f9..98940a32 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/BlockColorCalculator.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/BlockColorCalculator.java @@ -28,6 +28,7 @@ import java.awt.image.BufferedImage; import java.io.IOException; import java.util.HashMap; +import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; import java.util.function.Function; @@ -36,11 +37,13 @@ import com.flowpowered.math.vector.Vector2f; import com.flowpowered.math.vector.Vector2i; import com.flowpowered.math.vector.Vector3f; +import com.flowpowered.math.vector.Vector3i; import de.bluecolored.bluemap.core.util.ConfigUtils; import de.bluecolored.bluemap.core.util.MathUtils; import de.bluecolored.bluemap.core.world.Biome; import de.bluecolored.bluemap.core.world.Block; +import de.bluecolored.bluemap.core.world.World; import ninja.leaping.configurate.ConfigurationNode; public class BlockColorCalculator { @@ -97,31 +100,28 @@ public Vector3f getBlockColor(Block block){ public Vector3f getWaterAverageColor(Block block){ Vector3f color = Vector3f.ZERO; - - for (int x = -1; x <= 1; x++){ - for (int z = -1; z <= 1; z++){ - color = color.add(block.getRelativeBlock(x, 0, z).getBiome().getWaterColor()); - } + + int count = 0; + for (Biome biome : iterateAverageBiomes(block)) { + color = color.add(biome.getWaterColor()); + count++; } - return color.div(9f); + return color.div(count); } public Vector3f getFoliageAverageColor(Block block){ Vector3f color = Vector3f.ZERO; - for (int x = -1; x <= 1; x++){ - for (int z = -1; z <= 1; z++){ - color = color.add(getFoliageColor(block.getRelativeBlock(x, 0, z))); - } + int blocksAboveSeaLevel = Math.max(block.getPosition().getY() - block.getWorld().getSeaLevel(), 0); + + int count = 0; + for (Biome biome : iterateAverageBiomes(block)) { + color = color.add(getFoliageColor(biome, blocksAboveSeaLevel)); + count++; } - return color.div(9f); - } - - public Vector3f getFoliageColor(Block block){ - int blocksAboveSeaLevel = Math.max(block.getPosition().getY() - block.getWorld().getSeaLevel(), 0); - return getFoliageColor(block.getBiome(), blocksAboveSeaLevel); + return color.div(count); } public Vector3f getFoliageColor(Biome biome, int blocksAboveSeaLevel){ @@ -134,18 +134,15 @@ public Vector3f getFoliageColor(Biome biome, int blocksAboveSeaLevel){ public Vector3f getGrassAverageColor(Block block){ Vector3f color = Vector3f.ZERO; - for (int x = -1; x <= 1; x++){ - for (int z = -1; z <= 1; z++){ - color = color.add(getGrassColor(block.getRelativeBlock(x, 0, z))); - } + int blocksAboveSeaLevel = Math.max(block.getPosition().getY() - block.getWorld().getSeaLevel(), 0); + + int count = 0; + for (Biome biome : iterateAverageBiomes(block)) { + color = color.add(getGrassColor(biome, blocksAboveSeaLevel)); + count++; } - return color.div(9f); - } - - public Vector3f getGrassColor(Block block){ - int blocksAboveSeaLevel = Math.max(block.getPosition().getY() - block.getWorld().getSeaLevel(), 0); - return getGrassColor(block.getBiome(), blocksAboveSeaLevel); + return color.div(count); } public Vector3f getGrassColor(Biome biome, int blocksAboveSeaLevel){ @@ -168,6 +165,45 @@ private Vector2f getColorMapPosition(Biome biome, int blocksAboveSeaLevel){ float adjHumidity = (float) GenericMath.clamp(biome.getHumidity(), 0d, 1d) * adjTemp; return new Vector2f(1 - adjTemp, 1 - adjHumidity); } + + private Iterable iterateAverageBiomes(Block block){ + Vector3i pos = block.getPosition(); + Vector3i radius = new Vector3i(2, 1, 2); + + final World world = block.getWorld(); + final int sx = pos.getX() - radius.getX(), + sy = pos.getY() - radius.getY(), + sz = pos.getZ() - radius.getZ(); + final int mx = pos.getX() + radius.getX(), + my = pos.getY() + radius.getY(), + mz = pos.getZ() + radius.getZ(); + + return () -> new Iterator() { + private int x = sx, + y = sy, + z = sz; + + @Override + public boolean hasNext() { + return z < mz || y < my || x < mx; + } + + @Override + public Biome next() { + x++; + if (x > mx) { + x = sx; + y++; + } + if (y > my) { + y = sy; + z++; + } + + return world.getBiome(new Vector3i(x, y, z)); + } + }; + } public BufferedImage getFoliageMap() { return foliageMap; diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/SlicedWorld.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/SlicedWorld.java index 8cf9493b..487c25a1 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/SlicedWorld.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/SlicedWorld.java @@ -79,6 +79,11 @@ public int getMinY() { return world.getMinY(); } + @Override + public Biome getBiome(Vector3i pos) { + return world.getBiome(pos); + } + @Override public Block getBlock(Vector3i pos) { if (!isInside(pos)) return createAirBlock(pos); diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/World.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/World.java index 6717a867..f5674bce 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/World.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/World.java @@ -56,6 +56,11 @@ default int getMaxY() { default int getMinY() { return 0; } + + /** + * Returns the Biome on the specified position or the default biome if the block is not generated yet. + */ + Biome getBiome(Vector3i pos); /** * Returns the Block on the specified position or an air-block if the block is not generated yet. diff --git a/BlueMapCore/src/main/resources/blockColors.json b/BlueMapCore/src/main/resources/blockColors.json index f53cedb1..24cf4889 100644 --- a/BlueMapCore/src/main/resources/blockColors.json +++ b/BlueMapCore/src/main/resources/blockColors.json @@ -1,6 +1,7 @@ { "default": "@foliage", "minecraft:water": "@water", + "minecraft:cauldron": "@water", "minecraft:grass_block": "@grass", "minecraft:grass": "@grass", "minecraft:tall_grass": "@grass",