Merge branch 'mc/1.13' into mc/1.15

This commit is contained in:
Blue (Lukas Rieger) 2020-07-29 21:20:32 +02:00
commit cedcc8b502
16 changed files with 227 additions and 164 deletions

View File

@ -24,8 +24,6 @@
*/ */
package de.bluecolored.bluemap.common; package de.bluecolored.bluemap.common;
import java.io.IOException;
import com.flowpowered.math.vector.Vector2i; import com.flowpowered.math.vector.Vector2i;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
@ -68,7 +66,7 @@ public TileRenderer getTileRenderer() {
return tileRenderer; return tileRenderer;
} }
public void renderTile(Vector2i tile) throws IOException { public void renderTile(Vector2i tile) {
getTileRenderer().render(new WorldTile(getWorld(), tile)); getTileRenderer().render(new WorldTile(getWorld(), tile));
} }

View File

@ -163,17 +163,17 @@ private void renderThread() {
if (ticket != null) { if (ticket != null) {
try { try {
ticket.render(); ticket.render();
} catch (IOException e) { } catch (Exception e) {
if (ticket.getRenderAttempts() <= 1) { //catch possible runtime exceptions, display them, and wait a while .. then resurrect this render-thread
createTicket(ticket); Logger.global.logError("Unexpected exception in render-thread!", e);
} else { try {
Logger.global.logDebug("Failed to render tile " + ticket.getTile() + " of map '" + ticket.getMapType().getId() + "' after " + ticket.getRenderAttempts() + " render-attempts! (" + e.toString() + ")"); Thread.sleep(10000);
} } catch (InterruptedException interrupt) { break; }
} }
} else { } else {
try { try {
Thread.sleep(1000); // we don't need a super fast response time, so waiting a second is totally fine Thread.sleep(1000); // we don't need a super fast response time, so waiting a second is totally fine
} catch (InterruptedException e) { break; } } catch (InterruptedException interrupt) { break; }
} }
} }
} }

View File

@ -24,7 +24,6 @@
*/ */
package de.bluecolored.bluemap.common; package de.bluecolored.bluemap.common;
import java.io.IOException;
import java.util.Objects; import java.util.Objects;
import com.flowpowered.math.vector.Vector2i; import com.flowpowered.math.vector.Vector2i;
@ -34,25 +33,13 @@ public class RenderTicket {
private final MapType map; private final MapType map;
private final Vector2i tile; private final Vector2i tile;
private int renderAttempts;
private boolean successfullyRendered;
public RenderTicket(MapType map, Vector2i tile) { public RenderTicket(MapType map, Vector2i tile) {
this.map = map; this.map = map;
this.tile = tile; this.tile = tile;
this.renderAttempts = 0;
this.successfullyRendered = false;
} }
public synchronized void render() throws IOException { public synchronized void render() {
renderAttempts++;
if (!successfullyRendered) {
map.renderTile(tile); map.renderTile(tile);
successfullyRendered = true;
}
} }
public MapType getMapType() { public MapType getMapType() {
@ -63,10 +50,6 @@ public Vector2i getTile() {
return tile; return tile;
} }
public int getRenderAttempts() {
return renderAttempts;
}
@Override @Override
public int hashCode() { public int hashCode() {
return Objects.hash(map.getId(), tile); return Objects.hash(map.getId(), tile);

View File

@ -100,13 +100,19 @@ public void init() {
LiteralCommandNode<S> debugCommand = LiteralCommandNode<S> debugCommand =
literal("debug") literal("debug")
.requires(requirements("bluemap.debug")) .requires(requirements("bluemap.debug"))
.executes(this::debugCommand)
.then(literal("block")
.executes(this::debugBlockCommand)
.then(argument("world", StringArgumentType.string()).suggests(new WorldSuggestionProvider<>(plugin)) .then(argument("world", StringArgumentType.string()).suggests(new WorldSuggestionProvider<>(plugin))
.then(argument("x", DoubleArgumentType.doubleArg()) .then(argument("x", DoubleArgumentType.doubleArg())
.then(argument("y", DoubleArgumentType.doubleArg()) .then(argument("y", DoubleArgumentType.doubleArg())
.then(argument("z", DoubleArgumentType.doubleArg()) .then(argument("z", DoubleArgumentType.doubleArg())
.executes(this::debugCommand))))) .executes(this::debugBlockCommand))))))
.then(literal("cache")
.executes(this::debugClearCacheCommand))
.build(); .build();
LiteralCommandNode<S> pauseCommand = LiteralCommandNode<S> pauseCommand =
@ -310,17 +316,28 @@ public int reloadCommand(CommandContext<S> context) {
return 1; return 1;
} }
public int debugCommand(CommandContext<S> context) throws CommandSyntaxException { public int debugClearCacheCommand(CommandContext<S> context) throws CommandSyntaxException {
CommandSource source = commandSourceInterface.apply(context.getSource()); CommandSource source = commandSourceInterface.apply(context.getSource());
for (World world : plugin.getWorlds()) {
world.invalidateChunkCache();
}
source.sendMessage(Text.of(TextColor.GREEN, "All caches cleared!"));
return 1;
}
public int debugBlockCommand(CommandContext<S> context) throws CommandSyntaxException {
final CommandSource source = commandSourceInterface.apply(context.getSource());
// parse arguments // parse arguments
Optional<String> worldName = getOptionalArgument(context, "world", String.class); Optional<String> worldName = getOptionalArgument(context, "world", String.class);
Optional<Double> x = getOptionalArgument(context, "x", Double.class); Optional<Double> x = getOptionalArgument(context, "x", Double.class);
Optional<Double> y = getOptionalArgument(context, "y", Double.class); Optional<Double> y = getOptionalArgument(context, "y", Double.class);
Optional<Double> z = getOptionalArgument(context, "z", Double.class); Optional<Double> z = getOptionalArgument(context, "z", Double.class);
World world; final World world;
Vector3d position; final Vector3d position;
if (worldName.isPresent() && x.isPresent() && y.isPresent() && z.isPresent()) { if (worldName.isPresent() && x.isPresent() && y.isPresent() && z.isPresent()) {
world = parseWorld(worldName.get()).orElse(null); world = parseWorld(worldName.get()).orElse(null);
@ -340,7 +357,8 @@ public int debugCommand(CommandContext<S> context) throws CommandSyntaxException
} }
} }
// output debug info new Thread(() -> {
// collect and output debug info
Vector3i blockPos = position.floor().toInt(); Vector3i blockPos = position.floor().toInt();
Block block = world.getBlock(blockPos); Block block = world.getBlock(blockPos);
Block blockBelow = world.getBlock(blockPos.add(0, -1, 0)); Block blockBelow = world.getBlock(blockPos.add(0, -1, 0));
@ -349,21 +367,19 @@ public int debugCommand(CommandContext<S> context) throws CommandSyntaxException
String blockBelowIdMeta = ""; String blockBelowIdMeta = "";
if (world instanceof MCAWorld) { if (world instanceof MCAWorld) {
try {
Chunk chunk = ((MCAWorld) world).getChunk(MCAWorld.blockToChunk(blockPos)); Chunk chunk = ((MCAWorld) world).getChunk(MCAWorld.blockToChunk(blockPos));
if (chunk instanceof ChunkAnvil112) { if (chunk instanceof ChunkAnvil112) {
blockIdMeta = " (" + ((ChunkAnvil112) chunk).getBlockIdMeta(blockPos) + ")"; blockIdMeta = " (" + ((ChunkAnvil112) chunk).getBlockIdMeta(blockPos) + ")";
blockBelowIdMeta = " (" + ((ChunkAnvil112) chunk).getBlockIdMeta(blockPos.add(0, -1, 0)) + ")"; blockBelowIdMeta = " (" + ((ChunkAnvil112) chunk).getBlockIdMeta(blockPos.add(0, -1, 0)) + ")";
} }
} catch (IOException ex) {
Logger.global.logError("Failed to read chunk for debug!", ex);
}
} }
source.sendMessages(Lists.newArrayList( source.sendMessages(Lists.newArrayList(
Text.of(TextColor.GOLD, "Is generated: ", TextColor.WHITE, world.isChunkGenerated(world.blockPosToChunkPos(blockPos))),
Text.of(TextColor.GOLD, "Block at you: ", TextColor.WHITE, block, TextColor.GRAY, blockIdMeta), Text.of(TextColor.GOLD, "Block at you: ", TextColor.WHITE, block, TextColor.GRAY, blockIdMeta),
Text.of(TextColor.GOLD, "Block below you: ", TextColor.WHITE, blockBelow, TextColor.GRAY, blockBelowIdMeta) Text.of(TextColor.GOLD, "Block below you: ", TextColor.WHITE, blockBelow, TextColor.GRAY, blockBelowIdMeta)
)); ));
}).start();
return 1; return 1;
} }
@ -395,12 +411,13 @@ public int resumeCommand(CommandContext<S> context) {
} }
public int renderCommand(CommandContext<S> context) { public int renderCommand(CommandContext<S> context) {
CommandSource source = commandSourceInterface.apply(context.getSource()); final CommandSource source = commandSourceInterface.apply(context.getSource());
// parse world/map argument // parse world/map argument
Optional<String> worldOrMap = getOptionalArgument(context, "world|map", String.class); Optional<String> worldOrMap = getOptionalArgument(context, "world|map", String.class);
World world = null;
MapType map = null; final World world;
final MapType map;
if (worldOrMap.isPresent()) { if (worldOrMap.isPresent()) {
world = parseWorld(worldOrMap.get()).orElse(null); world = parseWorld(worldOrMap.get()).orElse(null);
@ -411,9 +428,12 @@ public int renderCommand(CommandContext<S> context) {
source.sendMessage(Text.of(TextColor.RED, "There is no ", helper.worldHelperHover(), " or ", helper.mapHelperHover(), " with this name: ", TextColor.WHITE, worldOrMap.get())); source.sendMessage(Text.of(TextColor.RED, "There is no ", helper.worldHelperHover(), " or ", helper.mapHelperHover(), " with this name: ", TextColor.WHITE, worldOrMap.get()));
return 0; return 0;
} }
} else {
map = null;
} }
} else { } else {
world = source.getWorld().orElse(null); world = source.getWorld().orElse(null);
map = null;
if (world == null) { if (world == null) {
source.sendMessage(Text.of(TextColor.RED, "Can't detect a world from this command-source, you'll have to define a world or a map to render!").setHoverText(Text.of(TextColor.GRAY, "/bluemap render <world|map>"))); source.sendMessage(Text.of(TextColor.RED, "Can't detect a world from this command-source, you'll have to define a world or a map to render!").setHoverText(Text.of(TextColor.GRAY, "/bluemap render <world|map>")));
@ -422,8 +442,8 @@ public int renderCommand(CommandContext<S> context) {
} }
// parse radius and center arguments // parse radius and center arguments
int radius = getOptionalArgument(context, "radius", Integer.class).orElse(-1); final int radius = getOptionalArgument(context, "radius", Integer.class).orElse(-1);
Vector2i center = null; final Vector2i center;
if (radius >= 0) { if (radius >= 0) {
Optional<Double> x = getOptionalArgument(context, "x", Double.class); Optional<Double> x = getOptionalArgument(context, "x", Double.class);
Optional<Double> z = getOptionalArgument(context, "z", Double.class); Optional<Double> z = getOptionalArgument(context, "z", Double.class);
@ -439,14 +459,18 @@ public int renderCommand(CommandContext<S> context) {
center = position.toVector2(true).floor().toInt(); center = position.toVector2(true).floor().toInt();
} }
} else {
center = null;
} }
// execute render // execute render
new Thread(() -> {
if (world != null) { if (world != null) {
helper.createWorldRenderTask(source, world, center, radius); helper.createWorldRenderTask(source, world, center, radius);
} else { } else {
helper.createMapRenderTask(source, map, center, radius); helper.createMapRenderTask(source, map, center, radius);
} }
}).start();
return 1; return 1;
} }

View File

@ -5,7 +5,7 @@ plugins {
dependencies { dependencies {
compile 'com.google.guava:guava:21.0' compile 'com.google.guava:guava:21.0'
compile 'com.google.code.gson:gson:2.8.0' compile 'com.google.code.gson:gson:2.8.0'
compile 'org.apache.commons:commons-lang3:3.5' compile 'org.apache.commons:commons-lang3:3.6'
compile group: 'commons-io', name: 'commons-io', version: '2.5' compile group: 'commons-io', name: 'commons-io', version: '2.5'
compile 'com.flowpowered:flow-math:1.0.3' compile 'com.flowpowered:flow-math:1.0.3'
compile 'ninja.leaping.configurate:configurate-hocon:3.3' compile 'ninja.leaping.configurate:configurate-hocon:3.3'

View File

@ -24,6 +24,8 @@
*/ */
package de.bluecolored.bluemap.core.mca; package de.bluecolored.bluemap.core.mca;
import java.util.Arrays;
import com.flowpowered.math.vector.Vector3i; import com.flowpowered.math.vector.Vector3i;
import de.bluecolored.bluemap.core.mca.mapping.BiomeMapper; import de.bluecolored.bluemap.core.mca.mapping.BiomeMapper;
@ -60,15 +62,21 @@ public ChunkAnvil112(MCAWorld world, CompoundTag chunkTag, boolean ignoreMissing
levelData.getBoolean("TerrainPopulated"); levelData.getBoolean("TerrainPopulated");
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? 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<CompoundTag>) levelData.getListTag("Sections"))) { for (CompoundTag sectionTag : ((ListTag<CompoundTag>) levelData.getListTag("Sections"))) {
Section section = new Section(sectionTag); Section section = new Section(sectionTag);
sections[section.getSectionY()] = section; if (section.getSectionY() >= 0 && section.getSectionY() < sections.length) sections[section.getSectionY()] = section;
}
} }
biomes = levelData.getByteArray("Biomes"); biomes = levelData.getByteArray("Biomes");
if (biomes == null || biomes.length == 0) { if (biomes == null || biomes.length == 0) {
biomes = new byte[2048]; biomes = new byte[256];
}
if (biomes.length < 256) {
biomes = Arrays.copyOf(biomes, 256);
} }
} }
@ -132,6 +140,11 @@ public Section(CompoundTag sectionData) {
this.blockLight = sectionData.getByteArray("BlockLight"); this.blockLight = sectionData.getByteArray("BlockLight");
this.skyLight = sectionData.getByteArray("SkyLight"); this.skyLight = sectionData.getByteArray("SkyLight");
this.data = sectionData.getByteArray("Data"); this.data = sectionData.getByteArray("Data");
if (blocks.length < 4096) blocks = Arrays.copyOf(blocks, 4096);
if (blockLight.length < 2048) blockLight = Arrays.copyOf(blockLight, 2048);
if (skyLight.length < 2048) skyLight = Arrays.copyOf(skyLight, 2048);
if (data.length < 2048) data = Arrays.copyOf(data, 2048);
} }
public int getSectionY() { public int getSectionY() {
@ -148,7 +161,7 @@ public BlockState getBlockState(Vector3i pos) {
int blockId = this.blocks[blockByteIndex] & 0xFF; int blockId = this.blocks[blockByteIndex] & 0xFF;
if (this.add.length > 0) { if (this.add.length > blockHalfByteIndex) {
blockId = blockId | (getByteHalf(this.add[blockHalfByteIndex], largeHalf) << 8); blockId = blockId | (getByteHalf(this.add[blockHalfByteIndex], largeHalf) << 8);
} }
@ -172,7 +185,7 @@ public String getBlockIdMeta(Vector3i pos) {
int blockId = this.blocks[blockByteIndex] & 0xFF; int blockId = this.blocks[blockByteIndex] & 0xFF;
if (this.add.length > 0) { if (this.add.length > blockHalfByteIndex) {
blockId = blockId | (getByteHalf(this.add[blockHalfByteIndex], largeHalf) << 8); blockId = blockId | (getByteHalf(this.add[blockHalfByteIndex], largeHalf) << 8);
} }

View File

@ -24,6 +24,7 @@
*/ */
package de.bluecolored.bluemap.core.mca; package de.bluecolored.bluemap.core.mca;
import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
@ -71,7 +72,7 @@ public ChunkAnvil113(MCAWorld world, CompoundTag chunkTag, boolean ignoreMissing
if (levelData.containsKey("Sections")) { if (levelData.containsKey("Sections")) {
for (CompoundTag sectionTag : ((ListTag<CompoundTag>) levelData.getListTag("Sections"))) { for (CompoundTag sectionTag : ((ListTag<CompoundTag>) levelData.getListTag("Sections"))) {
Section section = new Section(sectionTag); Section section = new Section(sectionTag);
if (section.getSectionY() >= 0) sections[section.getSectionY()] = section; if (section.getSectionY() >= 0 && section.getSectionY() < sections.length) sections[section.getSectionY()] = section;
} }
} }
@ -89,7 +90,11 @@ else if (tag instanceof IntArrayTag) {
} }
if (biomes == null || biomes.length == 0) { if (biomes == null || biomes.length == 0) {
biomes = new int[2048]; biomes = new int[256];
}
if (biomes.length < 256) {
biomes = Arrays.copyOf(biomes, 256);
} }
} }
@ -138,15 +143,19 @@ private class Section {
private long[] blocks; private long[] blocks;
private BlockState[] palette; private BlockState[] palette;
private int bitsPerBlock;
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public Section(CompoundTag sectionData) { public Section(CompoundTag sectionData) {
this.sectionY = sectionData.getByte("Y"); this.sectionY = sectionData.getByte("Y");
this.blockLight = sectionData.getByteArray("BlockLight"); this.blockLight = sectionData.getByteArray("BlockLight");
if (blockLight.length == 0) blockLight = new byte[2048];
this.skyLight = sectionData.getByteArray("SkyLight"); this.skyLight = sectionData.getByteArray("SkyLight");
if (skyLight.length == 0) skyLight = new byte[2048];
this.blocks = sectionData.getLongArray("BlockStates"); this.blocks = sectionData.getLongArray("BlockStates");
if (blocks.length < 256 && blocks.length > 0) blocks = Arrays.copyOf(blocks, 256);
if (blockLight.length < 2048 && blockLight.length > 0) blockLight = Arrays.copyOf(blockLight, 2048);
if (skyLight.length < 2048 && skyLight.length > 0) skyLight = Arrays.copyOf(skyLight, 2048);
//read block palette //read block palette
ListTag<CompoundTag> paletteTag = (ListTag<CompoundTag>) sectionData.getListTag("Palette"); ListTag<CompoundTag> paletteTag = (ListTag<CompoundTag>) sectionData.getListTag("Palette");
if (paletteTag != null) { if (paletteTag != null) {
@ -174,6 +183,8 @@ public Section(CompoundTag sectionData) {
} else { } else {
this.palette = new BlockState[0]; this.palette = new BlockState[0];
} }
this.bitsPerBlock = blocks.length * 64 / 4096; //64 bits per long and 4096 blocks per section
} }
public int getSectionY() { public int getSectionY() {
@ -187,7 +198,6 @@ public BlockState getBlockState(Vector3i pos) {
int y = pos.getY() & 0xF; int y = pos.getY() & 0xF;
int z = pos.getZ() & 0xF; int z = pos.getZ() & 0xF;
int blockIndex = y * 256 + z * 16 + x; 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 index = blockIndex * bitsPerBlock;
int firstLong = index >> 6; // index / 64 int firstLong = index >> 6; // index / 64
int bitoffset = index & 0x3F; // Math.floorMod(index, 64) int bitoffset = index & 0x3F; // Math.floorMod(index, 64)
@ -211,6 +221,8 @@ public BlockState getBlockState(Vector3i pos) {
} }
public LightData getLightData(Vector3i pos) { public LightData getLightData(Vector3i pos) {
if (blockLight.length == 0 && skyLight.length == 0) return LightData.ZERO;
int x = pos.getX() & 0xF; // Math.floorMod(pos.getX(), 16) int x = pos.getX() & 0xF; // Math.floorMod(pos.getX(), 16)
int y = pos.getY() & 0xF; int y = pos.getY() & 0xF;
int z = pos.getZ() & 0xF; int z = pos.getZ() & 0xF;
@ -218,8 +230,8 @@ public LightData getLightData(Vector3i pos) {
int blockHalfByteIndex = blockByteIndex >> 1; // blockByteIndex / 2 int blockHalfByteIndex = blockByteIndex >> 1; // blockByteIndex / 2
boolean largeHalf = (blockByteIndex & 0x1) != 0; // (blockByteIndex % 2) == 0 boolean largeHalf = (blockByteIndex & 0x1) != 0; // (blockByteIndex % 2) == 0
int blockLight = getByteHalf(this.blockLight[blockHalfByteIndex], largeHalf); int blockLight = this.blockLight.length > 0 ? getByteHalf(this.blockLight[blockHalfByteIndex], largeHalf) : 0;
int skyLight = getByteHalf(this.skyLight[blockHalfByteIndex], largeHalf); int skyLight = this.skyLight.length > 0 ? getByteHalf(this.skyLight[blockHalfByteIndex], largeHalf) : 0;
return new LightData(skyLight, blockLight); return new LightData(skyLight, blockLight);
} }

View File

@ -24,6 +24,7 @@
*/ */
package de.bluecolored.bluemap.core.mca; package de.bluecolored.bluemap.core.mca;
import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
@ -71,7 +72,7 @@ public ChunkAnvil115(MCAWorld world, CompoundTag chunkTag, boolean ignoreMissing
if (levelData.containsKey("Sections")) { if (levelData.containsKey("Sections")) {
for (CompoundTag sectionTag : ((ListTag<CompoundTag>) levelData.getListTag("Sections"))) { for (CompoundTag sectionTag : ((ListTag<CompoundTag>) levelData.getListTag("Sections"))) {
Section section = new Section(sectionTag); Section section = new Section(sectionTag);
if (section.getSectionY() >= 0) sections[section.getSectionY()] = section; if (section.getSectionY() >= 0 && section.getSectionY() < sections.length) sections[section.getSectionY()] = section;
} }
} }
@ -89,7 +90,11 @@ else if (tag instanceof IntArrayTag) {
} }
if (biomes == null || biomes.length == 0) { if (biomes == null || biomes.length == 0) {
biomes = new int[2048]; biomes = new int[1024];
}
if (biomes.length < 1024) {
biomes = Arrays.copyOf(biomes, 1024);
} }
} }
@ -145,11 +150,13 @@ private class Section {
public Section(CompoundTag sectionData) { public Section(CompoundTag sectionData) {
this.sectionY = sectionData.getByte("Y"); this.sectionY = sectionData.getByte("Y");
this.blockLight = sectionData.getByteArray("BlockLight"); this.blockLight = sectionData.getByteArray("BlockLight");
if (blockLight.length == 0) blockLight = new byte[2048];
this.skyLight = sectionData.getByteArray("SkyLight"); this.skyLight = sectionData.getByteArray("SkyLight");
if (skyLight.length == 0) skyLight = new byte[2048];
this.blocks = sectionData.getLongArray("BlockStates"); this.blocks = sectionData.getLongArray("BlockStates");
if (blocks.length < 256 && blocks.length > 0) blocks = Arrays.copyOf(blocks, 256);
if (blockLight.length < 2048 && blockLight.length > 0) blockLight = Arrays.copyOf(blockLight, 2048);
if (skyLight.length < 2048 && skyLight.length > 0) skyLight = Arrays.copyOf(skyLight, 2048);
//read block palette //read block palette
ListTag<CompoundTag> paletteTag = (ListTag<CompoundTag>) sectionData.getListTag("Palette"); ListTag<CompoundTag> paletteTag = (ListTag<CompoundTag>) sectionData.getListTag("Palette");
if (paletteTag != null) { if (paletteTag != null) {
@ -215,6 +222,8 @@ public BlockState getBlockState(Vector3i pos) {
} }
public LightData getLightData(Vector3i pos) { public LightData getLightData(Vector3i pos) {
if (blockLight.length == 0 && skyLight.length == 0) return LightData.ZERO;
int x = pos.getX() & 0xF; // Math.floorMod(pos.getX(), 16) int x = pos.getX() & 0xF; // Math.floorMod(pos.getX(), 16)
int y = pos.getY() & 0xF; int y = pos.getY() & 0xF;
int z = pos.getZ() & 0xF; int z = pos.getZ() & 0xF;
@ -222,8 +231,8 @@ public LightData getLightData(Vector3i pos) {
int blockHalfByteIndex = blockByteIndex >> 1; // blockByteIndex / 2 int blockHalfByteIndex = blockByteIndex >> 1; // blockByteIndex / 2
boolean largeHalf = (blockByteIndex & 0x1) != 0; // (blockByteIndex % 2) == 0 boolean largeHalf = (blockByteIndex & 0x1) != 0; // (blockByteIndex % 2) == 0
int blockLight = getByteHalf(this.blockLight[blockHalfByteIndex], largeHalf); int blockLight = this.blockLight.length > 0 ? getByteHalf(this.blockLight[blockHalfByteIndex], largeHalf) : 0;
int skyLight = getByteHalf(this.skyLight[blockHalfByteIndex], largeHalf); int skyLight = this.skyLight.length > 0 ? getByteHalf(this.skyLight[blockHalfByteIndex], largeHalf) : 0;
return new LightData(skyLight, blockLight); return new LightData(skyLight, blockLight);
} }

View File

@ -24,6 +24,7 @@
*/ */
package de.bluecolored.bluemap.core.mca; package de.bluecolored.bluemap.core.mca;
import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
@ -71,7 +72,7 @@ public ChunkAnvil116(MCAWorld world, CompoundTag chunkTag, boolean ignoreMissing
if (levelData.containsKey("Sections")) { if (levelData.containsKey("Sections")) {
for (CompoundTag sectionTag : ((ListTag<CompoundTag>) levelData.getListTag("Sections"))) { for (CompoundTag sectionTag : ((ListTag<CompoundTag>) levelData.getListTag("Sections"))) {
Section section = new Section(sectionTag); Section section = new Section(sectionTag);
if (section.getSectionY() >= 0) sections[section.getSectionY()] = section; if (section.getSectionY() >= 0 && section.getSectionY() < sections.length) sections[section.getSectionY()] = section;
} }
} }
@ -89,7 +90,11 @@ else if (tag instanceof IntArrayTag) {
} }
if (biomes == null || biomes.length == 0) { if (biomes == null || biomes.length == 0) {
biomes = new int[2048]; biomes = new int[1024];
}
if (biomes.length < 1024) {
biomes = Arrays.copyOf(biomes, 1024);
} }
} }
@ -146,11 +151,13 @@ private class Section {
public Section(CompoundTag sectionData) { public Section(CompoundTag sectionData) {
this.sectionY = sectionData.getByte("Y"); this.sectionY = sectionData.getByte("Y");
this.blockLight = sectionData.getByteArray("BlockLight"); this.blockLight = sectionData.getByteArray("BlockLight");
if (blockLight.length == 0) blockLight = new byte[2048];
this.skyLight = sectionData.getByteArray("SkyLight"); this.skyLight = sectionData.getByteArray("SkyLight");
if (skyLight.length == 0) skyLight = new byte[2048];
this.blocks = sectionData.getLongArray("BlockStates"); this.blocks = sectionData.getLongArray("BlockStates");
if (blocks.length < 256 && blocks.length > 0) blocks = Arrays.copyOf(blocks, 256);
if (blockLight.length < 2048 && blockLight.length > 0) blockLight = Arrays.copyOf(blockLight, 2048);
if (skyLight.length < 2048 && skyLight.length > 0) skyLight = Arrays.copyOf(skyLight, 2048);
//read block palette //read block palette
ListTag<CompoundTag> paletteTag = (ListTag<CompoundTag>) sectionData.getListTag("Palette"); ListTag<CompoundTag> paletteTag = (ListTag<CompoundTag>) sectionData.getListTag("Palette");
if (paletteTag != null) { if (paletteTag != null) {
@ -212,6 +219,8 @@ public BlockState getBlockState(Vector3i pos) {
} }
public LightData getLightData(Vector3i pos) { public LightData getLightData(Vector3i pos) {
if (blockLight.length == 0 && skyLight.length == 0) return LightData.ZERO;
int x = pos.getX() & 0xF; // Math.floorMod(pos.getX(), 16) int x = pos.getX() & 0xF; // Math.floorMod(pos.getX(), 16)
int y = pos.getY() & 0xF; int y = pos.getY() & 0xF;
int z = pos.getZ() & 0xF; int z = pos.getZ() & 0xF;
@ -219,8 +228,8 @@ public LightData getLightData(Vector3i pos) {
int blockHalfByteIndex = blockByteIndex >> 1; // blockByteIndex / 2 int blockHalfByteIndex = blockByteIndex >> 1; // blockByteIndex / 2
boolean largeHalf = (blockByteIndex & 0x1) != 0; // (blockByteIndex % 2) == 0 boolean largeHalf = (blockByteIndex & 0x1) != 0; // (blockByteIndex % 2) == 0
int blockLight = getByteHalf(this.blockLight[blockHalfByteIndex], largeHalf); int blockLight = this.blockLight.length > 0 ? getByteHalf(this.blockLight[blockHalfByteIndex], largeHalf) : 0;
int skyLight = getByteHalf(this.skyLight[blockHalfByteIndex], largeHalf); int skyLight = this.skyLight.length > 0 ? getByteHalf(this.skyLight[blockHalfByteIndex], largeHalf) : 0;
return new LightData(skyLight, blockLight); return new LightData(skyLight, blockLight);
} }

View File

@ -156,29 +156,25 @@ public BlockState getBlockState(Vector3i pos) {
@Override @Override
public Biome getBiome(Vector3i pos) { public Biome getBiome(Vector3i pos) {
try { if (pos.getY() < getMinY()) {
pos = new Vector3i(pos.getX(), getMinY(), pos.getZ());
} else if (pos.getY() > getMaxY()) {
pos = new Vector3i(pos.getX(), getMaxY(), pos.getZ());
}
Vector2i chunkPos = blockToChunk(pos); Vector2i chunkPos = blockToChunk(pos);
Chunk chunk = getChunk(chunkPos); Chunk chunk = getChunk(chunkPos);
return chunk.getBiome(pos); return chunk.getBiome(pos);
} catch (IOException ex) {
throw new RuntimeException("Unexpected IO-Exception trying to read world-data!", ex);
}
} }
@Override @Override
public Block getBlock(Vector3i pos) { public Block getBlock(Vector3i pos) {
if (pos.getY() < getMinY()) { if (pos.getY() < getMinY()) {
return new Block(this, BlockState.AIR, LightData.ZERO, Biome.DEFAULT, BlockProperties.TRANSPARENT, pos); return new Block(this, BlockState.AIR, LightData.ZERO, Biome.DEFAULT, BlockProperties.TRANSPARENT, pos);
} } else if (pos.getY() > getMaxY()) {
if (pos.getY() > getMaxY()) {
return new Block(this, BlockState.AIR, LightData.SKY, Biome.DEFAULT, BlockProperties.TRANSPARENT, pos); return new Block(this, BlockState.AIR, LightData.SKY, Biome.DEFAULT, BlockProperties.TRANSPARENT, pos);
} }
try {
Vector2i chunkPos = blockToChunk(pos); Vector2i chunkPos = blockToChunk(pos);
Chunk chunk = getChunk(chunkPos); Chunk chunk = getChunk(chunkPos);
BlockState blockState = getExtendedBlockState(chunk, pos); BlockState blockState = getExtendedBlockState(chunk, pos);
@ -186,10 +182,6 @@ public Block getBlock(Vector3i pos) {
Biome biome = chunk.getBiome(pos); Biome biome = chunk.getBiome(pos);
BlockProperties properties = blockPropertiesMapper.get(blockState); BlockProperties properties = blockPropertiesMapper.get(blockState);
return new Block(this, blockState, lightData, biome, properties, pos); return new Block(this, blockState, lightData, biome, properties, pos);
} catch (IOException ex) {
throw new RuntimeException("Unexpected IO-Exception trying to read world-data!", ex);
}
} }
private BlockState getExtendedBlockState(Chunk chunk, Vector3i pos) { private BlockState getExtendedBlockState(Chunk chunk, Vector3i pos) {
@ -204,26 +196,43 @@ private BlockState getExtendedBlockState(Chunk chunk, Vector3i pos) {
return blockState; return blockState;
} }
public Chunk getChunk(Vector2i chunkPos) throws IOException { public Chunk getChunk(Vector2i chunkPos) {
try { try {
Chunk chunk = CHUNK_CACHE.get(new WorldChunkHash(this, chunkPos), () -> this.loadChunk(chunkPos)); Chunk chunk = CHUNK_CACHE.get(new WorldChunkHash(this, chunkPos), () -> this.loadChunkOrEmpty(chunkPos, 2, 1000));
return chunk; return chunk;
} catch (UncheckedExecutionException | ExecutionException e) { } catch (UncheckedExecutionException | ExecutionException e) {
Throwable cause = e.getCause(); throw new RuntimeException(e.getCause());
}
if (cause instanceof IOException) {
throw (IOException) cause;
} }
else throw new IOException(cause); private Chunk loadChunkOrEmpty(Vector2i chunkPos, int tries, long tryInterval) {
Exception loadException = null;
for (int i = 0; i < tries; i++) {
try {
return loadChunk(chunkPos);
} catch (Exception e) {
loadException = e;
if (tryInterval > 0 && i+1 < tries) {
try {
Thread.sleep(tryInterval);
} catch (InterruptedException interrupt) {}
} }
} }
}
Logger.global.logDebug("Unexpected exception trying to load chunk (" + chunkPos + "):" + loadException);
return Chunk.empty(this, chunkPos);
}
private Chunk loadChunk(Vector2i chunkPos) throws IOException { private Chunk loadChunk(Vector2i chunkPos) throws IOException {
Vector2i regionPos = chunkToRegion(chunkPos); Vector2i regionPos = chunkToRegion(chunkPos);
Path regionPath = getMCAFilePath(regionPos); Path regionPath = getMCAFilePath(regionPos);
try (RandomAccessFile raf = new RandomAccessFile(regionPath.toFile(), "r")) { File regionFile = regionPath.toFile();
if (!regionFile.exists()) return Chunk.empty(this, chunkPos);
try (RandomAccessFile raf = new RandomAccessFile(regionFile, "r")) {
int xzChunk = Math.floorMod(chunkPos.getY(), 32) * 32 + Math.floorMod(chunkPos.getX(), 32); int xzChunk = Math.floorMod(chunkPos.getY(), 32) * 32 + Math.floorMod(chunkPos.getX(), 32);
@ -243,7 +252,7 @@ private Chunk loadChunk(Vector2i chunkPos) throws IOException {
byte compressionTypeByte = raf.readByte(); byte compressionTypeByte = raf.readByte();
CompressionType compressionType = CompressionType.getFromID(compressionTypeByte); CompressionType compressionType = CompressionType.getFromID(compressionTypeByte);
if (compressionType == null) { if (compressionType == null) {
throw new IOException("invalid compression type " + compressionTypeByte); throw new IOException("Invalid compression type " + compressionTypeByte);
} }
DataInputStream dis = new DataInputStream(new BufferedInputStream(compressionType.decompress(new FileInputStream(raf.getFD())))); DataInputStream dis = new DataInputStream(new BufferedInputStream(compressionType.decompress(new FileInputStream(raf.getFD()))));
@ -251,13 +260,15 @@ private Chunk loadChunk(Vector2i chunkPos) throws IOException {
if (tag instanceof CompoundTag) { if (tag instanceof CompoundTag) {
return Chunk.create(this, (CompoundTag) tag, ignoreMissingLightData); return Chunk.create(this, (CompoundTag) tag, ignoreMissingLightData);
} else { } else {
throw new IOException("invalid data tag: " + (tag == null ? "null" : tag.getClass().getName())); throw new IOException("Invalid data tag: " + (tag == null ? "null" : tag.getClass().getName()));
} }
} catch (Exception e) {
throw new IOException("Exception trying to load chunk (" + chunkPos + ")", e);
} }
} }
@Override @Override
public boolean isChunkGenerated(Vector2i chunkPos) throws IOException { public boolean isChunkGenerated(Vector2i chunkPos) {
Chunk chunk = getChunk(chunkPos); Chunk chunk = getChunk(chunkPos);
return chunk.isGenerated(); return chunk.isGenerated();
} }
@ -279,6 +290,9 @@ public Collection<Vector2i> getChunkList(long modifiedSinceMillis, Predicate<Vec
for (int x = 0; x < 32; x++) { for (int x = 0; x < 32; x++) {
for (int z = 0; z < 32; z++) { for (int z = 0; z < 32; z++) {
Vector2i chunk = new Vector2i(rX * 32 + x, rZ * 32 + z);
if (filter.test(chunk)) {
int xzChunk = z * 32 + x; int xzChunk = z * 32 + x;
raf.seek(xzChunk * 4 + 3); raf.seek(xzChunk * 4 + 3);
@ -293,10 +307,9 @@ public Collection<Vector2i> getChunkList(long modifiedSinceMillis, Predicate<Vec
timestamp |= raf.read() & 0xFF; timestamp |= raf.read() & 0xFF;
if (timestamp >= (modifiedSinceMillis / 1000)) { if (timestamp >= (modifiedSinceMillis / 1000)) {
Vector2i chunk = new Vector2i(rX * 32 + x, rZ * 32 + z);
if (filter.test(chunk)) {
chunks.add(chunk); chunks.add(chunk);
} }
} }
} }
} }

View File

@ -24,8 +24,6 @@
*/ */
package de.bluecolored.bluemap.core.render; package de.bluecolored.bluemap.core.render;
import java.io.IOException;
import de.bluecolored.bluemap.core.render.hires.HiresModel; import de.bluecolored.bluemap.core.render.hires.HiresModel;
import de.bluecolored.bluemap.core.render.hires.HiresModelManager; import de.bluecolored.bluemap.core.render.hires.HiresModelManager;
import de.bluecolored.bluemap.core.render.lowres.LowresModelManager; import de.bluecolored.bluemap.core.render.lowres.LowresModelManager;
@ -42,9 +40,8 @@ public TileRenderer(HiresModelManager hiresModelManager, LowresModelManager lowr
/** /**
* Renders the provided WorldTile (only) if the world is generated * Renders the provided WorldTile (only) if the world is generated
* @throws IOException If an IO-Exception occurs during the render
*/ */
public void render(WorldTile tile) throws IOException { public void render(WorldTile tile) {
//check if the region is generated before rendering, don't render if it's not generated //check if the region is generated before rendering, don't render if it's not generated
AABB area = hiresModelManager.getTileRegion(tile); AABB area = hiresModelManager.getTileRegion(tile);
if (!tile.getWorld().isAreaGenerated(area)) return; if (!tile.getWorld().isAreaGenerated(area)) return;

View File

@ -172,10 +172,10 @@ private Iterable<Biome> iterateAverageBiomes(Block block){
final World world = block.getWorld(); final World world = block.getWorld();
final int sx = pos.getX() - radius.getX(), final int sx = pos.getX() - radius.getX(),
sy = pos.getY() - radius.getY(), sy = Math.max(0, pos.getY() - radius.getY()),
sz = pos.getZ() - radius.getZ(); sz = pos.getZ() - radius.getZ();
final int mx = pos.getX() + radius.getX(), final int mx = pos.getX() + radius.getX(),
my = pos.getY() + radius.getY(), my = Math.min(255, pos.getY() + radius.getY()),
mz = pos.getZ() + radius.getZ(); mz = pos.getZ() + radius.getZ();
return () -> new Iterator<Biome>() { return () -> new Iterator<Biome>() {

View File

@ -24,7 +24,6 @@
*/ */
package de.bluecolored.bluemap.core.world; package de.bluecolored.bluemap.core.world;
import java.io.IOException;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.Collection; import java.util.Collection;
import java.util.UUID; import java.util.UUID;
@ -114,24 +113,24 @@ public Collection<Vector2i> getChunkList(long modifiedSince, Predicate<Vector2i>
} }
@Override @Override
public boolean isChunkGenerated(Vector2i chunkPos) throws IOException { public boolean isChunkGenerated(Vector2i chunkPos) {
if (!isInside(chunkPos)) return false; if (!isInside(chunkPos)) return false;
return world.isChunkGenerated(chunkPos); return world.isChunkGenerated(chunkPos);
} }
@Override @Override
public boolean isAreaGenerated(AABB area) throws IOException { public boolean isAreaGenerated(AABB area) {
return isAreaGenerated(area.getMin(), area.getMax()); return isAreaGenerated(area.getMin(), area.getMax());
} }
@Override @Override
public boolean isAreaGenerated(Vector3i blockMin, Vector3i blockMax) throws IOException { public boolean isAreaGenerated(Vector3i blockMin, Vector3i blockMax) {
return isAreaGenerated(blockPosToChunkPos(blockMin), blockPosToChunkPos(blockMax)); return isAreaGenerated(blockPosToChunkPos(blockMin), blockPosToChunkPos(blockMax));
} }
@Override @Override
public boolean isAreaGenerated(Vector2i chunkMin, Vector2i chunkMax) throws IOException { public boolean isAreaGenerated(Vector2i chunkMin, Vector2i chunkMax) {
if (!isInside(chunkMin) && if (!isInside(chunkMin) &&
!isInside(chunkMax) && !isInside(chunkMax) &&
!isInside(new Vector2i(chunkMin.getX(), chunkMax.getY())) && !isInside(new Vector2i(chunkMin.getX(), chunkMax.getY())) &&

View File

@ -110,7 +110,7 @@ public default Collection<Vector2i> getChunkList(long modifiedSince){
/** /**
* Returns true if and only if that chunk is fully generated and no world-generation or lighting has yet to be done. * Returns true if and only if that chunk is fully generated and no world-generation or lighting has yet to be done.
*/ */
public boolean isChunkGenerated(Vector2i chunkPos) throws IOException; public boolean isChunkGenerated(Vector2i chunkPos);
/** /**
@ -118,7 +118,7 @@ public default Collection<Vector2i> getChunkList(long modifiedSince){
* @param area The area to check * @param area The area to check
* @throws IOException * @throws IOException
*/ */
public default boolean isAreaGenerated(AABB area) throws IOException { public default boolean isAreaGenerated(AABB area) {
return isAreaGenerated(area.getMin(), area.getMax()); return isAreaGenerated(area.getMin(), area.getMax());
} }
@ -127,7 +127,7 @@ public default boolean isAreaGenerated(AABB area) throws IOException {
* @param area The area to check * @param area The area to check
* @throws IOException * @throws IOException
*/ */
public default boolean isAreaGenerated(Vector3i blockMin, Vector3i blockMax) throws IOException { public default boolean isAreaGenerated(Vector3i blockMin, Vector3i blockMax) {
return isAreaGenerated(blockPosToChunkPos(blockMin), blockPosToChunkPos(blockMax)); return isAreaGenerated(blockPosToChunkPos(blockMin), blockPosToChunkPos(blockMax));
} }
@ -136,7 +136,7 @@ public default boolean isAreaGenerated(Vector3i blockMin, Vector3i blockMax) thr
* @param area The area to check * @param area The area to check
* @throws IOException * @throws IOException
*/ */
public default boolean isAreaGenerated(Vector2i chunkMin, Vector2i chunkMax) throws IOException { public default boolean isAreaGenerated(Vector2i chunkMin, Vector2i chunkMax) {
for (int x = chunkMin.getX(); x <= chunkMax.getX(); x++) { for (int x = chunkMin.getX(); x <= chunkMax.getX(); x++) {
for (int z = chunkMin.getY(); z <= chunkMax.getY(); z++) { for (int z = chunkMin.getY(); z <= chunkMax.getY(); z++) {
if (!isChunkGenerated(new Vector2i(x, z))) return false; if (!isChunkGenerated(new Vector2i(x, z))) return false;

View File

@ -32,6 +32,12 @@ You found a bug, have another issue or a suggestion? Please create an issue [her
You are welcome to contribute! You are welcome to contribute!
Just create a pull request with your changes :) Just create a pull request with your changes :)
If you want to have your changes merged faster, make sure they are complete, documented and well tested!
The `master`-branch is for the latest version of minecraft.
The `mc/xx`-branches are for other minecraft-versions.
Changes that apply to all versions should be made on the `mc/1.13`-branch. This branch can be merged into `master` and every other `mc/xx` branch.
### Todo / planned features ### Todo / planned features
Here is a *(surely incomplete)* list of things that i want to include in future versions. *(They are not in any specific order. There is no guarantee that any of those things will ever be included.)* Here is a *(surely incomplete)* list of things that i want to include in future versions. *(They are not in any specific order. There is no guarantee that any of those things will ever be included.)*

View File

@ -1,6 +1,6 @@
org.gradle.jvmargs=-Xmx3G org.gradle.jvmargs=-Xmx3G
org.gradle.daemon=false org.gradle.daemon=false
coreVersion=0.8.0 coreVersion=0.9.0
targetVersion=mc1.15 targetVersion=mc1.15