mirror of
https://github.com/BlueMap-Minecraft/BlueMap.git
synced 2024-12-29 04:18:01 +01:00
Merge branch 'mc/1.13' into feature/fabric
This commit is contained in:
commit
b6f89ad5a8
@ -2,6 +2,7 @@ name: BlueMap
|
|||||||
description: "A 3d-map of your Minecraft worlds view-able in your browser using three.js (WebGL)"
|
description: "A 3d-map of your Minecraft worlds view-able in your browser using three.js (WebGL)"
|
||||||
main: de.bluecolored.bluemap.bukkit.BukkitPlugin
|
main: de.bluecolored.bluemap.bukkit.BukkitPlugin
|
||||||
version: ${version}
|
version: ${version}
|
||||||
|
api-version: 1.13
|
||||||
author: "Blue (TBlueF / Lukas Rieger)"
|
author: "Blue (TBlueF / Lukas Rieger)"
|
||||||
website: "https://github.com/BlueMap-Minecraft"
|
website: "https://github.com/BlueMap-Minecraft"
|
||||||
commands:
|
commands:
|
||||||
|
@ -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));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -265,7 +265,7 @@ public void readState(DataInputStream in, Collection<MapType> mapTypes) throws I
|
|||||||
tiles.add(tile);
|
tiles.add(tile);
|
||||||
}
|
}
|
||||||
|
|
||||||
createTickets(mapType, tiles);
|
if (mapType != null) createTickets(mapType, tiles);
|
||||||
}
|
}
|
||||||
|
|
||||||
//read tasks
|
//read tasks
|
||||||
|
@ -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++;
|
map.renderTile(tile);
|
||||||
|
|
||||||
if (!successfullyRendered) {
|
|
||||||
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);
|
||||||
|
@ -87,7 +87,7 @@ public void init() {
|
|||||||
// commands
|
// commands
|
||||||
LiteralCommandNode<S> baseCommand =
|
LiteralCommandNode<S> baseCommand =
|
||||||
literal("bluemap")
|
literal("bluemap")
|
||||||
.requires(requirements("bluemap.status"))
|
.requires(requirementsUnloaded("bluemap.status"))
|
||||||
.executes(this::statusCommand)
|
.executes(this::statusCommand)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
@ -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")
|
||||||
.then(argument("world", StringArgumentType.string()).suggests(new WorldSuggestionProvider<>(plugin))
|
.executes(this::debugBlockCommand)
|
||||||
.then(argument("x", DoubleArgumentType.doubleArg())
|
|
||||||
.then(argument("y", DoubleArgumentType.doubleArg())
|
.then(argument("world", StringArgumentType.string()).suggests(new WorldSuggestionProvider<>(plugin))
|
||||||
.then(argument("z", DoubleArgumentType.doubleArg())
|
.then(argument("x", DoubleArgumentType.doubleArg())
|
||||||
.executes(this::debugCommand)))))
|
.then(argument("y", DoubleArgumentType.doubleArg())
|
||||||
|
.then(argument("z", DoubleArgumentType.doubleArg())
|
||||||
|
.executes(this::debugBlockCommand))))))
|
||||||
|
|
||||||
|
.then(literal("cache")
|
||||||
|
.executes(this::debugClearCacheCommand))
|
||||||
|
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
LiteralCommandNode<S> pauseCommand =
|
LiteralCommandNode<S> pauseCommand =
|
||||||
@ -255,7 +261,7 @@ private Optional<World> parseWorld(String worldName) {
|
|||||||
|
|
||||||
private Optional<MapType> parseMap(String mapId) {
|
private Optional<MapType> parseMap(String mapId) {
|
||||||
for (MapType map : plugin.getMapTypes()) {
|
for (MapType map : plugin.getMapTypes()) {
|
||||||
if (map.getName().equalsIgnoreCase(mapId)) {
|
if (map.getId().equalsIgnoreCase(mapId)) {
|
||||||
return Optional.of(map);
|
return Optional.of(map);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -277,6 +283,11 @@ private Optional<UUID> parseUUID(String uuidString) {
|
|||||||
public int statusCommand(CommandContext<S> context) {
|
public int statusCommand(CommandContext<S> context) {
|
||||||
CommandSource source = commandSourceInterface.apply(context.getSource());
|
CommandSource source = commandSourceInterface.apply(context.getSource());
|
||||||
|
|
||||||
|
if (!plugin.isLoaded()) {
|
||||||
|
source.sendMessage(Text.of(TextColor.RED, "BlueMap is not loaded! Try /bluemap reload"));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
source.sendMessages(helper.createStatusMessage());
|
source.sendMessages(helper.createStatusMessage());
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@ -305,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);
|
||||||
@ -335,30 +357,29 @@ public int debugCommand(CommandContext<S> context) throws CommandSyntaxException
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// output debug info
|
new Thread(() -> {
|
||||||
Vector3i blockPos = position.floor().toInt();
|
// collect and output debug info
|
||||||
Block block = world.getBlock(blockPos);
|
Vector3i blockPos = position.floor().toInt();
|
||||||
Block blockBelow = world.getBlock(blockPos.add(0, -1, 0));
|
Block block = world.getBlock(blockPos);
|
||||||
|
Block blockBelow = world.getBlock(blockPos.add(0, -1, 0));
|
||||||
String blockIdMeta = "";
|
|
||||||
String blockBelowIdMeta = "";
|
String blockIdMeta = "";
|
||||||
|
String blockBelowIdMeta = "";
|
||||||
if (world instanceof MCAWorld) {
|
|
||||||
try {
|
if (world instanceof MCAWorld) {
|
||||||
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;
|
||||||
}
|
}
|
||||||
@ -390,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);
|
||||||
|
|
||||||
@ -406,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>")));
|
||||||
@ -417,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);
|
||||||
@ -434,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
|
||||||
if (world != null) {
|
new Thread(() -> {
|
||||||
helper.createWorldRenderTask(source, world, center, radius);
|
if (world != null) {
|
||||||
} else {
|
helper.createWorldRenderTask(source, world, center, radius);
|
||||||
helper.createMapRenderTask(source, map, center, radius);
|
} else {
|
||||||
}
|
helper.createMapRenderTask(source, map, center, radius);
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
@ -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'
|
||||||
|
@ -39,9 +39,13 @@ public abstract class Chunk {
|
|||||||
private final MCAWorld world;
|
private final MCAWorld world;
|
||||||
private final Vector2i chunkPos;
|
private final Vector2i chunkPos;
|
||||||
|
|
||||||
|
private final int dataVersion;
|
||||||
|
|
||||||
protected Chunk(MCAWorld world, Vector2i chunkPos) {
|
protected Chunk(MCAWorld world, Vector2i chunkPos) {
|
||||||
this.world = world;
|
this.world = world;
|
||||||
this.chunkPos = chunkPos;
|
this.chunkPos = chunkPos;
|
||||||
|
|
||||||
|
this.dataVersion = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Chunk(MCAWorld world, CompoundTag chunkTag) {
|
protected Chunk(MCAWorld world, CompoundTag chunkTag) {
|
||||||
@ -53,6 +57,8 @@ protected Chunk(MCAWorld world, CompoundTag chunkTag) {
|
|||||||
levelData.getInt("xPos"),
|
levelData.getInt("xPos"),
|
||||||
levelData.getInt("zPos")
|
levelData.getInt("zPos")
|
||||||
);
|
);
|
||||||
|
|
||||||
|
dataVersion = chunkTag.getInt("DataVersion");
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract boolean isGenerated();
|
public abstract boolean isGenerated();
|
||||||
@ -65,6 +71,10 @@ public MCAWorld getWorld() {
|
|||||||
return world;
|
return world;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getDataVersion() {
|
||||||
|
return dataVersion;
|
||||||
|
}
|
||||||
|
|
||||||
public abstract BlockState getBlockState(Vector3i pos);
|
public abstract BlockState getBlockState(Vector3i pos);
|
||||||
|
|
||||||
public abstract LightData getLightData(Vector3i pos);
|
public abstract LightData getLightData(Vector3i pos);
|
||||||
@ -76,7 +86,8 @@ public static Chunk create(MCAWorld world, CompoundTag chunkTag, boolean ignoreM
|
|||||||
|
|
||||||
if (version <= 1343) return new ChunkAnvil112(world, chunkTag, ignoreMissingLightData);
|
if (version <= 1343) return new ChunkAnvil112(world, chunkTag, ignoreMissingLightData);
|
||||||
if (version <= 1976) return new ChunkAnvil113(world, chunkTag, ignoreMissingLightData);
|
if (version <= 1976) return new ChunkAnvil113(world, chunkTag, ignoreMissingLightData);
|
||||||
return new ChunkAnvil115(world, chunkTag, ignoreMissingLightData);
|
if (version < 2534) return new ChunkAnvil115(world, chunkTag, ignoreMissingLightData);
|
||||||
|
return new ChunkAnvil116(world, chunkTag, ignoreMissingLightData);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Chunk empty(MCAWorld world, Vector2i chunkPos) {
|
public static Chunk empty(MCAWorld world, Vector2i chunkPos) {
|
||||||
|
@ -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?
|
||||||
for (CompoundTag sectionTag : ((ListTag<CompoundTag>) levelData.getListTag("Sections"))) {
|
if (levelData.containsKey("Sections")) {
|
||||||
Section section = new Section(sectionTag);
|
for (CompoundTag sectionTag : ((ListTag<CompoundTag>) levelData.getListTag("Sections"))) {
|
||||||
sections[section.getSectionY()] = section;
|
Section section = new Section(sectionTag);
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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,14 +143,18 @@ 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");
|
||||||
@ -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,21 +198,8 @@ 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 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);
|
|
||||||
|
|
||||||
|
long value = MCAMath.getValueFromLongStream(blocks, blockIndex, bitsPerBlock);
|
||||||
if (value >= palette.length) {
|
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)");
|
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 BlockState.MISSING;
|
||||||
@ -211,6 +209,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,24 +218,11 @@ 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 ? MCAMath.getByteHalf(this.blockLight[blockHalfByteIndex], largeHalf) : 0;
|
||||||
int skyLight = getByteHalf(this.skyLight[blockHalfByteIndex], largeHalf);
|
int skyLight = this.skyLight.length > 0 ? MCAMath.getByteHalf(this.skyLight[blockHalfByteIndex], largeHalf) : 0;
|
||||||
|
|
||||||
return new LightData(skyLight, blockLight);
|
return new LightData(skyLight, blockLight);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 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>
|
|
||||||
* 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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -139,14 +144,18 @@ 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");
|
||||||
@ -175,6 +184,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() {
|
||||||
@ -188,21 +199,8 @@ 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 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);
|
|
||||||
|
|
||||||
|
long value = MCAMath.getValueFromLongStream(blocks, blockIndex, bitsPerBlock);
|
||||||
if (value >= palette.length) {
|
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)");
|
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 BlockState.MISSING;
|
||||||
@ -212,6 +210,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,24 +219,11 @@ 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 ? MCAMath.getByteHalf(this.blockLight[blockHalfByteIndex], largeHalf) : 0;
|
||||||
int skyLight = getByteHalf(this.skyLight[blockHalfByteIndex], largeHalf);
|
int skyLight = this.skyLight.length > 0 ? MCAMath.getByteHalf(this.skyLight[blockHalfByteIndex], largeHalf) : 0;
|
||||||
|
|
||||||
return new LightData(skyLight, blockLight);
|
return new LightData(skyLight, blockLight);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 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>
|
|
||||||
* 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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,231 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||||
|
*
|
||||||
|
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||||
|
* 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.Arrays;
|
||||||
|
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 ChunkAnvil116 extends Chunk {
|
||||||
|
private BiomeMapper biomeIdMapper;
|
||||||
|
|
||||||
|
private boolean isGenerated;
|
||||||
|
private boolean hasLight;
|
||||||
|
private Section[] sections;
|
||||||
|
private int[] biomes;
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public ChunkAnvil116(MCAWorld world, CompoundTag chunkTag, boolean ignoreMissingLightData) {
|
||||||
|
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
|
||||||
|
hasLight = isGenerated;
|
||||||
|
|
||||||
|
if (!isGenerated && ignoreMissingLightData) {
|
||||||
|
isGenerated = !status.equals("empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
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"))) {
|
||||||
|
Section section = new Section(sectionTag);
|
||||||
|
if (section.getSectionY() >= 0 && section.getSectionY() < sections.length) 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[1024];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (biomes.length < 1024) {
|
||||||
|
biomes = Arrays.copyOf(biomes, 1024);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@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) {
|
||||||
|
if (!hasLight) return LightData.SKY;
|
||||||
|
|
||||||
|
int sectionY = MCAUtil.blockToChunk(pos.getY());
|
||||||
|
|
||||||
|
Section section = this.sections[sectionY];
|
||||||
|
if (section == null) return LightData.SKY;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
private int bitsPerBlock;
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public Section(CompoundTag sectionData) {
|
||||||
|
this.sectionY = sectionData.getByte("Y");
|
||||||
|
this.blockLight = sectionData.getByteArray("BlockLight");
|
||||||
|
this.skyLight = sectionData.getByteArray("SkyLight");
|
||||||
|
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
|
||||||
|
ListTag<CompoundTag> paletteTag = (ListTag<CompoundTag>) 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<String, String> properties = new HashMap<>();
|
||||||
|
|
||||||
|
if (stateTag.containsKey("Properties")) {
|
||||||
|
CompoundTag propertiesTag = stateTag.getCompoundTag("Properties");
|
||||||
|
for (Entry<String, Tag<?>> property : propertiesTag) {
|
||||||
|
properties.put(property.getKey().toLowerCase(), ((StringTag) property.getValue()).getValue().toLowerCase());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
palette[i] = new BlockState(id, properties);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.palette = new BlockState[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
this.bitsPerBlock = 32 - Integer.numberOfLeadingZeros(palette.length - 1);
|
||||||
|
if (this.bitsPerBlock < 4) this.bitsPerBlock = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
long value = MCAMath.getValueFromLongArray(blocks, blockIndex, 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) {
|
||||||
|
if (blockLight.length == 0 && skyLight.length == 0) return LightData.ZERO;
|
||||||
|
|
||||||
|
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 = this.blockLight.length > 0 ? MCAMath.getByteHalf(this.blockLight[blockHalfByteIndex], largeHalf) : 0;
|
||||||
|
int skyLight = this.skyLight.length > 0 ? MCAMath.getByteHalf(this.skyLight[blockHalfByteIndex], largeHalf) : 0;
|
||||||
|
|
||||||
|
return new LightData(skyLight, blockLight);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,74 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||||
|
*
|
||||||
|
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
public class MCAMath {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Having a long array where each long contains as many values as fit in it without overflowing, returning the "valueIndex"-th value when each value has "bitsPerValue" bits.
|
||||||
|
*/
|
||||||
|
public static long getValueFromLongArray(long[] data, int valueIndex, int bitsPerValue) {
|
||||||
|
int valuesPerLong = 64 / bitsPerValue;
|
||||||
|
int longIndex = valueIndex / valuesPerLong;
|
||||||
|
int bitIndex = (valueIndex % valuesPerLong) * bitsPerValue;
|
||||||
|
|
||||||
|
long value = data[longIndex] >>> bitIndex;
|
||||||
|
|
||||||
|
return value & (0xFFFFFFFFFFFFFFFFL >>> -bitsPerValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Treating the long array "data" as a continuous stream of bits, returning the "valueIndex"-th value when each value has "bitsPerValue" bits.
|
||||||
|
*/
|
||||||
|
public static long getValueFromLongStream(long[] data, int valueIndex, int bitsPerValue) {
|
||||||
|
int bitIndex = valueIndex * bitsPerValue;
|
||||||
|
int firstLong = bitIndex >> 6; // index / 64
|
||||||
|
int bitoffset = bitIndex & 0x3F; // Math.floorMod(index, 64)
|
||||||
|
|
||||||
|
long value = data[firstLong] >>> bitoffset;
|
||||||
|
|
||||||
|
if (bitoffset > 0 && firstLong + 1 < data.length) {
|
||||||
|
long value2 = data[firstLong + 1];
|
||||||
|
value2 = value2 << -bitoffset;
|
||||||
|
value = value | value2;
|
||||||
|
}
|
||||||
|
|
||||||
|
return value & (0xFFFFFFFFFFFFFFFFL >>> -bitsPerValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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>
|
||||||
|
* The value is treated as an unsigned byte.
|
||||||
|
*/
|
||||||
|
public static int getByteHalf(int value, boolean largeHalf) {
|
||||||
|
value = value & 0xFF;
|
||||||
|
if (largeHalf) {
|
||||||
|
value = value >> 4;
|
||||||
|
}
|
||||||
|
value = value & 0xF;
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -38,7 +38,6 @@
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
@ -156,40 +155,32 @@ 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());
|
||||||
Vector2i chunkPos = blockToChunk(pos);
|
} else if (pos.getY() > getMaxY()) {
|
||||||
Chunk chunk = getChunk(chunkPos);
|
pos = new Vector3i(pos.getX(), getMaxY(), pos.getZ());
|
||||||
return chunk.getBiome(pos);
|
|
||||||
|
|
||||||
} catch (IOException ex) {
|
|
||||||
throw new RuntimeException("Unexpected IO-Exception trying to read world-data!", ex);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Vector2i chunkPos = blockToChunk(pos);
|
||||||
|
Chunk chunk = getChunk(chunkPos);
|
||||||
|
return chunk.getBiome(pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
@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);
|
||||||
|
Chunk chunk = getChunk(chunkPos);
|
||||||
Vector2i chunkPos = blockToChunk(pos);
|
BlockState blockState = getExtendedBlockState(chunk, pos);
|
||||||
Chunk chunk = getChunk(chunkPos);
|
LightData lightData = chunk.getLightData(pos);
|
||||||
BlockState blockState = getExtendedBlockState(chunk, pos);
|
Biome biome = chunk.getBiome(pos);
|
||||||
LightData lightData = chunk.getLightData(pos);
|
BlockProperties properties = blockPropertiesMapper.get(blockState);
|
||||||
Biome biome = chunk.getBiome(pos);
|
return new Block(this, blockState, lightData, biome, properties, pos);
|
||||||
BlockProperties properties = blockPropertiesMapper.get(blockState);
|
|
||||||
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 +195,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 +251,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 +259,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,24 +289,26 @@ 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++) {
|
||||||
int xzChunk = z * 32 + x;
|
Vector2i chunk = new Vector2i(rX * 32 + x, rZ * 32 + z);
|
||||||
|
if (filter.test(chunk)) {
|
||||||
raf.seek(xzChunk * 4 + 3);
|
|
||||||
int size = raf.readByte() * 4096;
|
int xzChunk = z * 32 + x;
|
||||||
|
|
||||||
if (size == 0) continue;
|
raf.seek(xzChunk * 4 + 3);
|
||||||
|
int size = raf.readByte() * 4096;
|
||||||
raf.seek(xzChunk * 4 + 4096);
|
|
||||||
int timestamp = raf.read() << 24;
|
if (size == 0) continue;
|
||||||
timestamp |= (raf.read() & 0xFF) << 16;
|
|
||||||
timestamp |= (raf.read() & 0xFF) << 8;
|
raf.seek(xzChunk * 4 + 4096);
|
||||||
timestamp |= raf.read() & 0xFF;
|
int timestamp = raf.read() << 24;
|
||||||
|
timestamp |= (raf.read() & 0xFF) << 16;
|
||||||
if (timestamp >= (modifiedSinceMillis / 1000)) {
|
timestamp |= (raf.read() & 0xFF) << 8;
|
||||||
Vector2i chunk = new Vector2i(rX * 32 + x, rZ * 32 + z);
|
timestamp |= raf.read() & 0xFF;
|
||||||
if (filter.test(chunk)) {
|
|
||||||
|
if (timestamp >= (modifiedSinceMillis / 1000)) {
|
||||||
chunks.add(chunk);
|
chunks.add(chunk);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -493,7 +505,7 @@ public WorldChunkHash(MCAWorld world, Vector2i chunk) {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return Objects.hash(world, chunk);
|
return (world.hashCode() * 31 + chunk.getX()) * 31 + chunk.getY();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -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;
|
||||||
|
@ -189,8 +189,17 @@ private void createElementFace(BlockStateModel model, TransformedBlockModelResou
|
|||||||
Texture texture = face.getTexture();
|
Texture texture = face.getTexture();
|
||||||
int textureId = texture.getId();
|
int textureId = texture.getId();
|
||||||
|
|
||||||
ExtendedFace f1 = new ExtendedFace(c0, c1, c2, uvs[0], uvs[1], uvs[2], textureId);
|
ExtendedFace f1;
|
||||||
ExtendedFace f2 = new ExtendedFace(c0, c2, c3, uvs[0], uvs[2], uvs[3], textureId);
|
ExtendedFace f2;
|
||||||
|
|
||||||
|
try {
|
||||||
|
f1 = new ExtendedFace(c0, c1, c2, uvs[0], uvs[1], uvs[2], textureId);
|
||||||
|
f2 = new ExtendedFace(c0, c2, c3, uvs[0], uvs[2], uvs[3], textureId);
|
||||||
|
} catch (ArithmeticException ex) {
|
||||||
|
// This error is thrown when a model defined a face that has no surface (all 3 points are on one line)
|
||||||
|
// we catch it here and simply ignore the face
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
//tint the face
|
//tint the face
|
||||||
Vector3f color = Vector3f.ONE;
|
Vector3f color = Vector3f.ONE;
|
||||||
|
@ -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>() {
|
||||||
|
@ -269,7 +269,6 @@ private BlockModelResource buildNoReset(String modelPath, boolean renderElements
|
|||||||
|
|
||||||
for (Entry<Object, ? extends ConfigurationNode> entry : config.getNode("textures").getChildrenMap().entrySet()) {
|
for (Entry<Object, ? extends ConfigurationNode> entry : config.getNode("textures").getChildrenMap().entrySet()) {
|
||||||
if (entry.getKey().equals(JSON_COMMENT)) continue;
|
if (entry.getKey().equals(JSON_COMMENT)) continue;
|
||||||
|
|
||||||
textures.putIfAbsent(entry.getKey().toString(), entry.getValue().getString(null));
|
textures.putIfAbsent(entry.getKey().toString(), entry.getValue().getString(null));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -426,6 +425,8 @@ private Vector4f readVector4f(ConfigurationNode node) throws ParseResourceExcept
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Texture getTexture(String key) throws NoSuchElementException, FileNotFoundException, IOException {
|
private Texture getTexture(String key) throws NoSuchElementException, FileNotFoundException, IOException {
|
||||||
|
if (key.isEmpty() || key.equals("#")) throw new NoSuchElementException("Empty texture key or name!");
|
||||||
|
|
||||||
if (key.charAt(0) == '#') {
|
if (key.charAt(0) == '#') {
|
||||||
String value = textures.get(key.substring(1));
|
String value = textures.get(key.substring(1));
|
||||||
if (value == null) throw new NoSuchElementException("There is no texture defined for the key " + key);
|
if (value == null) throw new NoSuchElementException("There is no texture defined for the key " + key);
|
||||||
|
@ -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())) &&
|
||||||
|
@ -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;
|
||||||
|
@ -9,5 +9,6 @@
|
|||||||
"minecraft:large_fern": "@grass",
|
"minecraft:large_fern": "@grass",
|
||||||
"minecraft:redstone_wire": "#ff0000",
|
"minecraft:redstone_wire": "#ff0000",
|
||||||
"minecraft:birch_leaves": "#86a863",
|
"minecraft:birch_leaves": "#86a863",
|
||||||
"minecraft:spruce_leaves": "#51946b"
|
"minecraft:spruce_leaves": "#51946b",
|
||||||
|
"minecraft:stonecutter": "#ffffff"
|
||||||
}
|
}
|
@ -25,6 +25,7 @@
|
|||||||
import { ShaderChunk } from 'three';
|
import { ShaderChunk } from 'three';
|
||||||
|
|
||||||
const HIRES_VERTEX_SHADER = `
|
const HIRES_VERTEX_SHADER = `
|
||||||
|
#define EPSILON 1e-6
|
||||||
${ShaderChunk.logdepthbuf_pars_vertex}
|
${ShaderChunk.logdepthbuf_pars_vertex}
|
||||||
|
|
||||||
attribute float ao;
|
attribute float ao;
|
||||||
|
@ -25,6 +25,7 @@
|
|||||||
import { ShaderChunk } from 'three';
|
import { ShaderChunk } from 'three';
|
||||||
|
|
||||||
const LOWRES_VERTEX_SHADER = `
|
const LOWRES_VERTEX_SHADER = `
|
||||||
|
#define EPSILON 1e-6
|
||||||
${ShaderChunk.logdepthbuf_pars_vertex}
|
${ShaderChunk.logdepthbuf_pars_vertex}
|
||||||
|
|
||||||
varying vec3 vPosition;
|
varying vec3 vPosition;
|
||||||
|
@ -20,7 +20,7 @@ configurations {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
minecraft 'net.minecraftforge:forge:1.15.2-31.1.0'
|
minecraft 'net.minecraftforge:forge:1.14.4-28.2.0'
|
||||||
|
|
||||||
include (project(':BlueMapCommon')) {
|
include (project(':BlueMapCommon')) {
|
||||||
//exclude dependencies provided by forge
|
//exclude dependencies provided by forge
|
||||||
@ -33,7 +33,7 @@ dependencies {
|
|||||||
}
|
}
|
||||||
|
|
||||||
build.dependsOn shadowJar {
|
build.dependsOn shadowJar {
|
||||||
destinationDir = file '../build/unsupported'
|
destinationDir = file '../build/release'
|
||||||
archiveFileName = "BlueMap-${version}-forge.jar"
|
archiveFileName = "BlueMap-${version}-forge.jar"
|
||||||
|
|
||||||
configurations = [project.configurations.include]
|
configurations = [project.configurations.include]
|
||||||
|
@ -20,6 +20,6 @@ A 3d-map of your Minecraft worlds view-able in your browser using three.js (WebG
|
|||||||
[[dependencies.bluemap]]
|
[[dependencies.bluemap]]
|
||||||
modId="minecraft"
|
modId="minecraft"
|
||||||
mandatory=true
|
mandatory=true
|
||||||
versionRange="[1.15.2]"
|
versionRange="[1.14.4]"
|
||||||
ordering="NONE"
|
ordering="NONE"
|
||||||
side="SERVER"
|
side="SERVER"
|
@ -1,5 +1,5 @@
|
|||||||
dependencies {
|
dependencies {
|
||||||
shadow "org.spongepowered:spongeapi:7.1.0-SNAPSHOT"
|
shadow "org.spongepowered:spongeapi:7.2.0"
|
||||||
compile group: 'org.bstats', name: 'bstats-sponge-lite', version: '1.5'
|
compile group: 'org.bstats', name: 'bstats-sponge-lite', version: '1.5'
|
||||||
|
|
||||||
compile (project(':BlueMapCommon')) {
|
compile (project(':BlueMapCommon')) {
|
||||||
|
@ -39,7 +39,9 @@
|
|||||||
import org.spongepowered.api.event.game.GameReloadEvent;
|
import org.spongepowered.api.event.game.GameReloadEvent;
|
||||||
import org.spongepowered.api.event.game.state.GameStartingServerEvent;
|
import org.spongepowered.api.event.game.state.GameStartingServerEvent;
|
||||||
import org.spongepowered.api.event.game.state.GameStoppingEvent;
|
import org.spongepowered.api.event.game.state.GameStoppingEvent;
|
||||||
|
import org.spongepowered.api.plugin.PluginContainer;
|
||||||
import org.spongepowered.api.scheduler.SpongeExecutorService;
|
import org.spongepowered.api.scheduler.SpongeExecutorService;
|
||||||
|
import org.spongepowered.api.util.Tristate;
|
||||||
import org.spongepowered.api.world.World;
|
import org.spongepowered.api.world.World;
|
||||||
import org.spongepowered.api.world.storage.WorldProperties;
|
import org.spongepowered.api.world.storage.WorldProperties;
|
||||||
|
|
||||||
@ -133,6 +135,7 @@ public void registerListener(ServerEventListener listener) {
|
|||||||
@Override
|
@Override
|
||||||
public void unregisterAllListeners() {
|
public void unregisterAllListeners() {
|
||||||
Sponge.getEventManager().unregisterPluginListeners(this);
|
Sponge.getEventManager().unregisterPluginListeners(this);
|
||||||
|
Sponge.getEventManager().registerListeners(this, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -161,4 +164,17 @@ public File getConfigFolder() {
|
|||||||
return configurationDir.toFile();
|
return configurationDir.toFile();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isMetricsEnabled(boolean configValue) {
|
||||||
|
PluginContainer pluginContainer = Sponge.getPluginManager().fromInstance(this).orElse(null);
|
||||||
|
if (pluginContainer != null) {
|
||||||
|
Tristate metricsEnabled = Sponge.getMetricsConfigManager().getCollectionState(pluginContainer);
|
||||||
|
if (metricsEnabled != Tristate.UNDEFINED) {
|
||||||
|
return metricsEnabled == Tristate.TRUE ? true : false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Sponge.getMetricsConfigManager().getGlobalCollectionState().asBoolean();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
12
README.md
12
README.md
@ -32,18 +32,22 @@ 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.)*
|
||||||
|
|
||||||
|
- live player positions
|
||||||
|
- fabric version
|
||||||
- render more tile-entities (banners, shulker-chests, etc..)
|
- render more tile-entities (banners, shulker-chests, etc..)
|
||||||
- render entities (armor-stands, item-frames, maybe even cows and such..)
|
- render entities (armor-stands, item-frames, maybe even cows and such..)
|
||||||
- configurable markers / regions
|
|
||||||
- marker / region API
|
|
||||||
- free-flight-controls
|
- free-flight-controls
|
||||||
- live player positions
|
|
||||||
- more configurations
|
- more configurations
|
||||||
- easier mod-integration
|
- easier mod-integration
|
||||||
- BlueMap as forge mod
|
|
||||||
- ability to display the world-border
|
- ability to display the world-border
|
||||||
- animated textures (if feasible)
|
- animated textures (if feasible)
|
||||||
- add support for models in obj format (if feasible)
|
- add support for models in obj format (if feasible)
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
org.gradle.jvmargs=-Xmx3G
|
org.gradle.jvmargs=-Xmx3G
|
||||||
org.gradle.daemon=false
|
org.gradle.daemon=false
|
||||||
|
|
||||||
coreVersion=0.7.0
|
coreVersion=0.9.0
|
||||||
|
|
||||||
targetVersion=mc1.13
|
targetVersion=mc1.13
|
Loading…
Reference in New Issue
Block a user