Core rewrite done

This commit is contained in:
Lukas Rieger (Blue) 2023-11-08 13:27:51 +01:00
parent 069e1747fa
commit 32311faccb
No known key found for this signature in database
GPG Key ID: 2D09EC5ED2687FF2
72 changed files with 1411 additions and 2375 deletions

View File

@ -42,11 +42,12 @@ import de.bluecolored.bluemap.core.MinecraftVersion;
import de.bluecolored.bluemap.core.debug.StateDumper;
import de.bluecolored.bluemap.core.logger.Logger;
import de.bluecolored.bluemap.core.map.BmMap;
import de.bluecolored.bluemap.core.mca.MCAWorld;
import de.bluecolored.bluemap.core.resources.resourcepack.ResourcePack;
import de.bluecolored.bluemap.core.storage.Storage;
import de.bluecolored.bluemap.core.util.FileHelper;
import de.bluecolored.bluemap.core.util.Key;
import de.bluecolored.bluemap.core.world.World;
import de.bluecolored.bluemap.core.world.mca.MCAWorld;
import org.apache.commons.io.FileUtils;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.configurate.ConfigurateException;
@ -241,7 +242,7 @@ public class BlueMapService implements Closeable {
if (world == null) {
try {
Logger.global.logInfo("Loading world '" + worldId + "' (" + worldFolder.toAbsolutePath().normalize() + ")...");
world = new MCAWorld(worldFolder, mapConfig.getWorldSkyLight(), mapConfig.isIgnoreMissingLightData());
world = MCAWorld.load(worldFolder, new Key("overworld")); //TODO
worlds.put(worldId, world);
} catch (IOException ex) {
throw new ConfigurationException(

View File

@ -24,6 +24,8 @@
*/
package de.bluecolored.bluemap.common;
import com.google.gson.FieldNamingPolicy;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import de.bluecolored.bluemap.common.config.WebappConfig;
import de.bluecolored.bluemap.core.BlueMap;
@ -46,6 +48,11 @@ import java.util.zip.ZipFile;
public class WebFilesManager {
private static final Gson GSON = ResourcesGson.addAdapter(new GsonBuilder())
.setFieldNamingPolicy(FieldNamingPolicy.IDENTITY)
.setPrettyPrinting() // enable pretty printing for easy editing
.create();
private final Path webRoot;
private Settings settings;
@ -60,7 +67,7 @@ public class WebFilesManager {
public void loadSettings() throws IOException {
try (BufferedReader reader = Files.newBufferedReader(getSettingsFile())) {
this.settings = ResourcesGson.INSTANCE.fromJson(reader, Settings.class);
this.settings = GSON.fromJson(reader, Settings.class);
}
}
@ -68,10 +75,7 @@ public class WebFilesManager {
FileHelper.createDirectories(getSettingsFile().getParent());
try (BufferedWriter writer = Files.newBufferedWriter(getSettingsFile(),
StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)) {
ResourcesGson.addAdapter(new GsonBuilder())
.setPrettyPrinting() // enable pretty printing for easy editing
.create()
.toJson(this.settings, writer);
GSON.toJson(this.settings, writer);
}
}

View File

@ -22,51 +22,27 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package de.bluecolored.bluemap.core.world;
package de.bluecolored.bluemap.common.config.typeserializer;
public class EmptyChunk implements Chunk {
import de.bluecolored.bluemap.core.util.Key;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.configurate.ConfigurationNode;
import org.spongepowered.configurate.serialize.SerializationException;
import org.spongepowered.configurate.serialize.TypeSerializer;
public static final Chunk INSTANCE = new EmptyChunk();
import java.lang.reflect.Type;
public class KeyTypeSerializer implements TypeSerializer<Key> {
@Override
public boolean isGenerated() {
return false;
public Key deserialize(Type type, ConfigurationNode node) throws SerializationException {
String formatted = node.getString();
return formatted != null ? new Key(node.getString()) : null;
}
@Override
public long getInhabitedTime() {
return 0;
public void serialize(Type type, @Nullable Key obj, ConfigurationNode node) throws SerializationException {
if (obj != null) node.set(obj.getFormatted());
}
@Override
public BlockState getBlockState(int x, int y, int z) {
return BlockState.AIR;
}
@Override
public LightData getLightData(int x, int y, int z, LightData target) {
return target.set(0, 0);
}
@Override
public String getBiome(int x, int y, int z) {
return Biome.DEFAULT.getFormatted();
}
@Override
public int getMaxY(int x, int z) {
return 255;
}
@Override
public int getMinY(int x, int z) {
return 0;
}
@Override
public int getWorldSurfaceY(int x, int z) { return 0; }
@Override
public int getOceanFloorY(int x, int z) { return 0; }
}

View File

@ -53,7 +53,7 @@ import de.bluecolored.bluemap.core.logger.Logger;
import de.bluecolored.bluemap.core.map.BmMap;
import de.bluecolored.bluemap.core.map.MapRenderState;
import de.bluecolored.bluemap.core.storage.Storage;
import de.bluecolored.bluemap.core.world.Block;
import de.bluecolored.bluemap.core.world.block.Block;
import de.bluecolored.bluemap.core.world.World;
import java.io.IOException;
@ -714,14 +714,14 @@ public class Commands<S> {
try {
List<BmMap> maps = new ArrayList<>();
if (worldToRender != null) {
var world = plugin.getServerInterface().getWorld(worldToRender.getSaveFolder()).orElse(null);
var world = plugin.getServerInterface().getWorld(worldToRender.getWorldFolder()).orElse(null);
if (world != null) world.persistWorldChanges();
for (BmMap map : plugin.getMaps().values()) {
if (map.getWorld().getSaveFolder().equals(worldToRender.getSaveFolder())) maps.add(map);
if (map.getWorld().getWorldFolder().equals(worldToRender.getWorldFolder())) maps.add(map);
}
} else {
var world = plugin.getServerInterface().getWorld(mapToRender.getWorld().getSaveFolder()).orElse(null);
var world = plugin.getServerInterface().getWorld(mapToRender.getWorld().getWorldFolder()).orElse(null);
if (world != null) world.persistWorldChanges();
maps.add(mapToRender);
@ -832,7 +832,7 @@ public class Commands<S> {
source.sendMessage(Text.of(TextColor.BLUE, "Worlds loaded by BlueMap:"));
for (var entry : plugin.getWorlds().entrySet()) {
source.sendMessage(Text.of(TextColor.GRAY, " - ", TextColor.WHITE, entry.getValue().getName()).setHoverText(Text.of(entry.getValue().getSaveFolder(), TextColor.GRAY, " (" + entry.getKey() + ")")));
source.sendMessage(Text.of(TextColor.GRAY, " - ", TextColor.WHITE, entry.getValue().getName()).setHoverText(Text.of(entry.getValue().getWorldFolder(), TextColor.GRAY, " (" + entry.getKey() + ")")));
}
return 1;

View File

@ -27,7 +27,7 @@ package de.bluecolored.bluemap.common.rendermanager;
import com.flowpowered.math.vector.Vector2i;
import de.bluecolored.bluemap.api.debug.DebugDump;
import de.bluecolored.bluemap.core.map.BmMap;
import de.bluecolored.bluemap.core.world.Grid;
import de.bluecolored.bluemap.core.util.Grid;
import de.bluecolored.bluemap.core.world.World;
import java.util.ArrayList;

View File

@ -27,11 +27,14 @@ package de.bluecolored.bluemap.common.rendermanager;
import com.flowpowered.math.vector.Vector2i;
import com.flowpowered.math.vector.Vector2l;
import de.bluecolored.bluemap.api.debug.DebugDump;
import de.bluecolored.bluemap.core.logger.Logger;
import de.bluecolored.bluemap.core.map.BmMap;
import de.bluecolored.bluemap.core.util.Grid;
import de.bluecolored.bluemap.core.world.Chunk;
import de.bluecolored.bluemap.core.world.Grid;
import de.bluecolored.bluemap.core.world.ChunkConsumer;
import de.bluecolored.bluemap.core.world.Region;
import java.io.IOException;
import java.util.*;
import java.util.function.Predicate;
import java.util.stream.Collectors;
@ -71,13 +74,17 @@ public class WorldRegionRenderTask implements RenderTask {
Set<Vector2l> tileSet = new HashSet<>();
startTime = System.currentTimeMillis();
//Logger.global.logInfo("Starting: " + worldRegion);
long changesSince = 0;
if (!force) changesSince = map.getRenderState().getRenderTime(worldRegion);
// collect chunks
long changesSince = force ? 0 : map.getRenderState().getRenderTime(worldRegion);
Region region = map.getWorld().getRegion(worldRegion.getX(), worldRegion.getY());
Collection<Vector2i> chunks = region.listChunks(changesSince);
Collection<Vector2i> chunks = new ArrayList<>(1024);
try {
region.iterateAllChunks((ChunkConsumer.ListOnly) (x, z, timestamp) -> {
if (timestamp >= changesSince) chunks.add(new Vector2i(x, z));
});
} catch (IOException ex) {
Logger.global.logWarning("Failed to read region " + worldRegion + " from world " + map.getWorld().getWorldFolder() + " (" + ex + ")");
}
Grid tileGrid = map.getHiresModelManager().getTileGrid();
Grid chunkGrid = map.getWorld().getChunkGrid();
@ -115,6 +122,10 @@ public class WorldRegionRenderTask implements RenderTask {
.collect(Collectors.toCollection(ArrayDeque::new));
if (tiles.isEmpty()) complete();
else {
// preload chunks
map.getWorld().preloadRegionChunks(worldRegion.getX(), worldRegion.getY());
}
}
@Override
@ -132,7 +143,6 @@ public class WorldRegionRenderTask implements RenderTask {
this.atWork++;
}
//Logger.global.logInfo("Working on " + worldRegion + " - Tile " + tile);
if (tileRenderPreconditions(tile)) {
map.renderTile(tile); // <- actual work
}
@ -163,6 +173,7 @@ public class WorldRegionRenderTask implements RenderTask {
for (int z = minChunk.getY(); z <= maxChunk.getY(); z++) {
Chunk chunk = map.getWorld().getChunk(x, z);
if (!chunk.isGenerated()) return false;
if (!chunk.hasLightData() && !map.getMapSettings().isIgnoreMissingLightData()) return false;
if (chunk.getInhabitedTime() >= minInhab) isInhabited = true;
}
}
@ -184,8 +195,6 @@ public class WorldRegionRenderTask implements RenderTask {
private void complete() {
map.getRenderState().setRenderTime(worldRegion, startTime);
//Logger.global.logInfo("Done with: " + worldRegion);
}
@Override

View File

@ -27,7 +27,12 @@ fun String.runCommand(): String = ProcessBuilder(split("\\s(?=(?:[^'\"`]*(['\"`]
}
val gitHash = "git rev-parse --verify HEAD".runCommand()
val clean = "git status --porcelain".runCommand().isEmpty()
var clean = false;
try {
clean = "git status --porcelain".runCommand().isEmpty();
} catch (ex: TimeoutException) {
println("Failed to run 'git status --porcelain', assuming dirty version.")
}
val lastTag = if ("git tag".runCommand().isEmpty()) "" else "git describe --tags --abbrev=0".runCommand()
val lastVersion = if (lastTag.isEmpty()) "dev" else lastTag.substring(1) // remove the leading 'v'
val commits = "git rev-list --count $lastTag..HEAD".runCommand()
@ -61,7 +66,7 @@ dependencies {
api ("commons-io:commons-io:2.5")
api ("org.spongepowered:configurate-hocon:4.1.2")
api ("org.spongepowered:configurate-gson:4.1.2")
api ("com.github.BlueMap-Minecraft:BlueNBT:v1.2.1")
api ("com.github.BlueMap-Minecraft:BlueNBT:v1.3.0")
api ("org.apache.commons:commons-dbcp2:2.9.0")
api ("io.airlift:aircompressor:0.24")

View File

@ -25,6 +25,8 @@
package de.bluecolored.bluemap.core.map;
import com.flowpowered.math.vector.Vector2i;
import com.google.gson.FieldNamingPolicy;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import de.bluecolored.bluemap.api.debug.DebugDump;
import de.bluecolored.bluemap.api.gson.MarkerGson;
@ -35,7 +37,7 @@ import de.bluecolored.bluemap.core.map.lowres.LowresTileManager;
import de.bluecolored.bluemap.core.resources.adapter.ResourcesGson;
import de.bluecolored.bluemap.core.resources.resourcepack.ResourcePack;
import de.bluecolored.bluemap.core.storage.Storage;
import de.bluecolored.bluemap.core.world.Grid;
import de.bluecolored.bluemap.core.util.Grid;
import de.bluecolored.bluemap.core.world.World;
import java.io.*;
@ -55,6 +57,11 @@ public class BmMap {
public static final String META_FILE_MARKERS = "live/markers.json";
public static final String META_FILE_PLAYERS = "live/players.json";
private static final Gson GSON = ResourcesGson.addAdapter(new GsonBuilder())
.setFieldNamingPolicy(FieldNamingPolicy.IDENTITY)
.registerTypeAdapter(BmMap.class, new MapSettingsSerializer())
.create();
private final String id;
private final String name;
private final String worldId;
@ -197,10 +204,7 @@ public class BmMap {
OutputStream out = storage.writeMeta(id, META_FILE_SETTINGS);
Writer writer = new OutputStreamWriter(out, StandardCharsets.UTF_8)
) {
ResourcesGson.addAdapter(new GsonBuilder())
.registerTypeAdapter(BmMap.class, new MapSettingsSerializer())
.create()
.toJson(this, writer);
GSON.toJson(this, writer);
} catch (Exception ex) {
Logger.global.logError("Failed to save settings for map '" + getId() + "'!", ex);
}

View File

@ -24,6 +24,9 @@
*/
package de.bluecolored.bluemap.core.map;
import com.google.gson.FieldNamingPolicy;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonIOException;
import de.bluecolored.bluemap.api.debug.DebugDump;
import de.bluecolored.bluemap.core.resources.ResourcePath;
@ -40,6 +43,10 @@ import java.util.Map;
@DebugDump
public class TextureGallery {
private static final Gson GSON = ResourcesGson.addAdapter(new GsonBuilder())
.setFieldNamingPolicy(FieldNamingPolicy.IDENTITY)
.create();
private final Map<ResourcePath<Texture>, Integer> ordinalMap;
private int nextId;
@ -83,7 +90,7 @@ public class TextureGallery {
});
try (Writer writer = new OutputStreamWriter(out)) {
ResourcesGson.INSTANCE.toJson(textures, Texture[].class, writer);
GSON.toJson(textures, Texture[].class, writer);
} catch (JsonIOException ex) {
throw new IOException(ex);
}
@ -92,7 +99,7 @@ public class TextureGallery {
public static TextureGallery readTexturesFile(InputStream in) throws IOException {
TextureGallery gallery = new TextureGallery();
try (Reader reader = new InputStreamReader(in)) {
Texture[] textures = ResourcesGson.INSTANCE.fromJson(reader, Texture[].class);
Texture[] textures = GSON.fromJson(reader, Texture[].class);
if (textures == null) throw new IOException("Texture data is empty!");
gallery.nextId = textures.length;
for (int ordinal = 0; ordinal < textures.length; ordinal++) {

View File

@ -31,7 +31,7 @@ import de.bluecolored.bluemap.core.map.TextureGallery;
import de.bluecolored.bluemap.core.map.TileMetaConsumer;
import de.bluecolored.bluemap.core.resources.resourcepack.ResourcePack;
import de.bluecolored.bluemap.core.storage.Storage;
import de.bluecolored.bluemap.core.world.Grid;
import de.bluecolored.bluemap.core.util.Grid;
import de.bluecolored.bluemap.core.world.World;
import java.io.IOException;

View File

@ -30,7 +30,8 @@ import de.bluecolored.bluemap.core.map.TileMetaConsumer;
import de.bluecolored.bluemap.core.map.hires.blockmodel.BlockStateModelFactory;
import de.bluecolored.bluemap.core.resources.resourcepack.ResourcePack;
import de.bluecolored.bluemap.core.util.math.Color;
import de.bluecolored.bluemap.core.world.BlockNeighborhood;
import de.bluecolored.bluemap.core.world.Chunk;
import de.bluecolored.bluemap.core.world.block.BlockNeighborhood;
import de.bluecolored.bluemap.core.world.World;
public class HiresModelRenderer {
@ -73,8 +74,9 @@ public class HiresModelRenderer {
columnColor.set(0, 0, 0, 0, true);
if (renderSettings.isInsideRenderBoundaries(x, z)) {
minY = Math.max(min.getY(), world.getMinY(x, z));
maxY = Math.min(max.getY(), world.getMaxY(x, z));
Chunk chunk = world.getChunkAtBlock(x, z);
minY = Math.max(min.getY(), chunk.getMinY(x, z));
maxY = Math.min(max.getY(), chunk.getMaxY(x, z));
for (y = minY; y <= maxY; y++) {
block.set(x, y, z);

View File

@ -78,6 +78,10 @@ public interface RenderSettings {
return true;
}
default boolean isIgnoreMissingLightData() {
return false;
}
default boolean isInsideRenderBoundaries(int x, int z) {
Vector3i min = getMinPos();
Vector3i max = getMaxPos();

View File

@ -31,7 +31,7 @@ import de.bluecolored.bluemap.core.resources.resourcepack.ResourcePack;
import de.bluecolored.bluemap.core.resources.resourcepack.blockmodel.BlockModel;
import de.bluecolored.bluemap.core.resources.resourcepack.blockstate.Variant;
import de.bluecolored.bluemap.core.util.math.Color;
import de.bluecolored.bluemap.core.world.BlockNeighborhood;
import de.bluecolored.bluemap.core.world.block.BlockNeighborhood;
import de.bluecolored.bluemap.core.world.BlockState;
import java.util.ArrayList;

View File

@ -42,9 +42,9 @@ import de.bluecolored.bluemap.core.util.math.Color;
import de.bluecolored.bluemap.core.util.math.MatrixM3f;
import de.bluecolored.bluemap.core.util.math.VectorM2f;
import de.bluecolored.bluemap.core.util.math.VectorM3f;
import de.bluecolored.bluemap.core.world.BlockNeighborhood;
import de.bluecolored.bluemap.core.world.block.BlockNeighborhood;
import de.bluecolored.bluemap.core.world.BlockState;
import de.bluecolored.bluemap.core.world.ExtendedBlock;
import de.bluecolored.bluemap.core.world.block.ExtendedBlock;
/**
* A model builder for all liquid blocks
@ -71,7 +71,6 @@ public class LiquidModelBuilder {
private BlockModel modelResource;
private BlockModelView blockModel;
private Color blockColor;
private boolean isCave;
public LiquidModelBuilder(ResourcePack resourcePack, TextureGallery textureGallery, RenderSettings renderSettings) {
this.resourcePack = resourcePack;
@ -100,17 +99,13 @@ public class LiquidModelBuilder {
this.blockModel = blockModel;
this.blockColor = color;
this.isCave =
this.block.getY() < renderSettings.getRemoveCavesBelowY() &&
this.block.getY() < block.getChunk().getOceanFloorY(block.getX(), block.getZ()) + renderSettings.getCaveDetectionOceanFloor();
build();
}
private final Color tintcolor = new Color();
private void build() {
// filter out blocks that are in a "cave" that should not be rendered
if (this.isCave && (renderSettings.isCaveDetectionUsesBlockLight() ? block.getBlockLightLevel() : block.getSunLightLevel()) == 0f) return;
if (this.block.isCave() && (renderSettings.isCaveDetectionUsesBlockLight() ? block.getBlockLightLevel() : block.getSunLightLevel()) == 0f) return;
int level = blockState.getLiquidLevel();
if (level < 8 && !(level == 0 && isSameLiquid(block.getNeighborBlock(0, 1, 0)))){

View File

@ -45,9 +45,9 @@ import de.bluecolored.bluemap.core.util.math.Color;
import de.bluecolored.bluemap.core.util.math.MatrixM4f;
import de.bluecolored.bluemap.core.util.math.VectorM2f;
import de.bluecolored.bluemap.core.util.math.VectorM3f;
import de.bluecolored.bluemap.core.world.BlockNeighborhood;
import de.bluecolored.bluemap.core.world.block.BlockNeighborhood;
import de.bluecolored.bluemap.core.world.BlockProperties;
import de.bluecolored.bluemap.core.world.ExtendedBlock;
import de.bluecolored.bluemap.core.world.block.ExtendedBlock;
import de.bluecolored.bluemap.core.world.LightData;
/**
@ -74,7 +74,6 @@ public class ResourceModelBuilder {
private BlockModelView blockModel;
private Color blockColor;
private float blockColorOpacity;
private boolean isCave;
public ResourceModelBuilder(ResourcePack resourcePack, TextureGallery textureGallery, RenderSettings renderSettings) {
this.resourcePack = resourcePack;
@ -95,10 +94,6 @@ public class ResourceModelBuilder {
this.variant = variant;
this.modelResource = variant.getModel().getResource();
this.isCave =
this.block.getY() < renderSettings.getRemoveCavesBelowY() &&
this.block.getY() < block.getChunk().getOceanFloorY(block.getX(), block.getZ()) + renderSettings.getCaveDetectionOceanFloor();
this.tintColor.set(0, 0, 0, -1, true);
// render model
@ -201,7 +196,7 @@ public class ResourceModelBuilder {
int blockLight = Math.max(blockLightData.getBlockLight(), facedLightData.getBlockLight());
// filter out faces that are in a "cave" that should not be rendered
if (isCave && (renderSettings.isCaveDetectionUsesBlockLight() ? Math.max(blockLight, sunLight) : sunLight) == 0f) return;
if (block.isCave() && (renderSettings.isCaveDetectionUsesBlockLight() ? Math.max(blockLight, sunLight) : sunLight) == 0f) return;
// initialize the faces
blockModel.initialize();

View File

@ -31,7 +31,7 @@ import de.bluecolored.bluemap.core.logger.Logger;
import de.bluecolored.bluemap.core.storage.Storage;
import de.bluecolored.bluemap.core.util.Vector2iCache;
import de.bluecolored.bluemap.core.util.math.Color;
import de.bluecolored.bluemap.core.world.Grid;
import de.bluecolored.bluemap.core.util.Grid;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.jetbrains.annotations.Nullable;

View File

@ -27,7 +27,7 @@ package de.bluecolored.bluemap.core.map.lowres;
import de.bluecolored.bluemap.core.map.TileMetaConsumer;
import de.bluecolored.bluemap.core.storage.Storage;
import de.bluecolored.bluemap.core.util.math.Color;
import de.bluecolored.bluemap.core.world.Grid;
import de.bluecolored.bluemap.core.util.Grid;
public class LowresTileManager implements TileMetaConsumer {

View File

@ -1,252 +0,0 @@
/*
* 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;
@SuppressWarnings("FieldMayBeFinal")
public class ChunkAnvil113 /* extends MCAChunk */ {
private static final long[] EMPTY_LONG_ARRAY = new long[0];
/*
private boolean isGenerated;
private boolean hasLight;
private long inhabitedTime;
private Section[] sections;
private int[] biomes;
private long[] oceanFloorHeights = EMPTY_LONG_ARRAY;
private long[] worldSurfaceHeights = EMPTY_LONG_ARRAY;
@SuppressWarnings("unchecked")
public ChunkAnvil113(MCAWorld world, CompoundTag chunkTag) {
super(world, chunkTag);
CompoundTag levelData = chunkTag.getCompoundTag("Level");
String status = levelData.getString("Status");
this.isGenerated = status.equals("full") ||
status.equals("fullchunk") ||
status.equals("postprocessed");
this.hasLight = isGenerated;
this.inhabitedTime = levelData.getLong("InhabitedTime");
if (!isGenerated && getWorld().isIgnoreMissingLightData()) {
isGenerated = !status.equals("empty");
}
if (levelData.containsKey("Heightmaps")) {
CompoundTag heightmapsTag = levelData.getCompoundTag("Heightmaps");
this.worldSurfaceHeights = heightmapsTag.getLongArray("WORLD_SURFACE");
this.oceanFloorHeights = heightmapsTag.getLongArray("OCEAN_FLOOR");
}
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;
}
} else {
sections = new Section[0];
}
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[256];
}
if (biomes.length < 256) {
biomes = Arrays.copyOf(biomes, 256);
}
}
@Override
public boolean isGenerated() {
return isGenerated;
}
@Override
public long getInhabitedTime() {
return inhabitedTime;
}
@Override
public BlockState getBlockState(int x, int y, int z) {
int sectionY = y >> 4;
if (sectionY < 0 || sectionY >= this.sections.length) return BlockState.AIR;
Section section = this.sections[sectionY];
if (section == null) return BlockState.AIR;
return section.getBlockState(x, y, z);
}
@Override
public LightData getLightData(int x, int y, int z, LightData target) {
if (!hasLight) return target.set(getWorld().getSkyLight(), 0);
int sectionY = y >> 4;
if (sectionY < 0 || sectionY >= this.sections.length)
return (y < 0) ? target.set(0, 0) : target.set(getWorld().getSkyLight(), 0);
Section section = this.sections[sectionY];
if (section == null) return target.set(getWorld().getSkyLight(), 0);
return section.getLightData(x, y, z, target);
}
@Override
public String getBiome(int x, int y, int z) {
x &= 0xF; z &= 0xF;
int biomeIntIndex = z * 16 + x;
if (biomeIntIndex >= this.biomes.length) return Biome.DEFAULT.getFormatted();
return LegacyBiomes.idFor(biomes[biomeIntIndex]);
}
@Override
public int getWorldSurfaceY(int x, int z) {
if (this.worldSurfaceHeights.length < 36) return 0;
x &= 0xF; z &= 0xF;
return (int) MCAMath.getValueFromLongStream(this.worldSurfaceHeights, z * 16 + x, 9);
}
@Override
public int getOceanFloorY(int x, int z) {
if (this.oceanFloorHeights.length < 36) return 0;
x &= 0xF; z &= 0xF;
return (int) MCAMath.getValueFromLongStream(this.oceanFloorHeights, z * 16 + x, 9);
}
private static 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.get("Y", NumberTag.class).asInt();
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 = this.blocks.length >> 6; // available longs * 64 (bits per long) / 4096 (blocks per section) (floored result)
}
public int getSectionY() {
return sectionY;
}
public BlockState getBlockState(int x, int y, int z) {
if (palette.length == 1) return palette[0];
if (blocks.length == 0) return BlockState.AIR;
x &= 0xF; y &= 0xF; z &= 0xF; // Math.floorMod(pos.getX(), 16)
int blockIndex = y * 256 + z * 16 + x;
long value = MCAMath.getValueFromLongStream(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(int x, int y, int z, LightData target) {
if (blockLight.length == 0 && skyLight.length == 0) return target.set(0, 0);
x &= 0xF; y &= 0xF; z &= 0xF; // Math.floorMod(pos.getX(), 16)
int blockByteIndex = y * 256 + z * 16 + x;
int blockHalfByteIndex = blockByteIndex >> 1; // blockByteIndex / 2
boolean largeHalf = (blockByteIndex & 0x1) != 0; // (blockByteIndex % 2) == 0
return target.set(
this.skyLight.length > 0 ? MCAMath.getByteHalf(this.skyLight[blockHalfByteIndex], largeHalf) : 0,
this.blockLight.length > 0 ? MCAMath.getByteHalf(this.blockLight[blockHalfByteIndex], largeHalf) : 0
);
}
}
*/
}

View File

@ -1,257 +0,0 @@
/*
* 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;
@SuppressWarnings("FieldMayBeFinal")
public class ChunkAnvil115 /* extends MCAChunk */ {
private static final long[] EMPTY_LONG_ARRAY = new long[0];
/*
private boolean isGenerated;
private boolean hasLight;
private long inhabitedTime;
private Section[] sections;
private int[] biomes;
private long[] oceanFloorHeights = EMPTY_LONG_ARRAY;
private long[] worldSurfaceHeights = EMPTY_LONG_ARRAY;
@SuppressWarnings("unchecked")
public ChunkAnvil115(MCAWorld world, CompoundTag chunkTag) {
super(world, chunkTag);
CompoundTag levelData = chunkTag.getCompoundTag("Level");
String status = levelData.getString("Status");
this.isGenerated = status.equals("full");
this.hasLight = isGenerated;
this.inhabitedTime = levelData.getLong("InhabitedTime");
if (!isGenerated && getWorld().isIgnoreMissingLightData()) {
isGenerated = !status.equals("empty");
}
if (levelData.containsKey("Heightmaps")) {
CompoundTag heightmapsTag = levelData.getCompoundTag("Heightmaps");
this.worldSurfaceHeights = heightmapsTag.getLongArray("WORLD_SURFACE");
this.oceanFloorHeights = heightmapsTag.getLongArray("OCEAN_FLOOR");
}
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;
}
} else {
sections = new Section[0];
}
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 long getInhabitedTime() {
return inhabitedTime;
}
@Override
public BlockState getBlockState(int x, int y, int z) {
int sectionY = y >> 4;
if (sectionY < 0 || sectionY >= this.sections.length) return BlockState.AIR;
Section section = this.sections[sectionY];
if (section == null) return BlockState.AIR;
return section.getBlockState(x, y, z);
}
@Override
public LightData getLightData(int x, int y, int z, LightData target) {
if (!hasLight) return target.set(getWorld().getSkyLight(), 0);
int sectionY = y >> 4;
if (sectionY < 0 || sectionY >= this.sections.length)
return (y < 0) ? target.set(0, 0) : target.set(getWorld().getSkyLight(), 0);
Section section = this.sections[sectionY];
if (section == null) return target.set(getWorld().getSkyLight(), 0);
return section.getLightData(x, y, z, target);
}
@Override
public String getBiome(int x, int y, int z) {
x = (x & 0xF) / 4; // Math.floorMod(pos.getX(), 16)
z = (z & 0xF) / 4;
y = y / 4;
int biomeIntIndex = y * 16 + z * 4 + x;
if (biomeIntIndex < 0) return Biome.DEFAULT.getFormatted();
if (biomeIntIndex >= this.biomes.length) return Biome.DEFAULT.getFormatted();
return LegacyBiomes.idFor(biomes[biomeIntIndex]);
}
@Override
public int getMaxY(int x, int z) {
return sections.length * 16 + 15;
}
@Override
public int getWorldSurfaceY(int x, int z) {
if (this.worldSurfaceHeights.length < 36) return 0;
x &= 0xF; z &= 0xF;
return (int) MCAMath.getValueFromLongStream(this.worldSurfaceHeights, z * 16 + x, 9);
}
@Override
public int getOceanFloorY(int x, int z) {
if (this.oceanFloorHeights.length < 36) return 0;
x &= 0xF; z &= 0xF;
return (int) MCAMath.getValueFromLongStream(this.oceanFloorHeights, z * 16 + x, 9);
}
private static 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.get("Y", NumberTag.class).asInt();
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 = this.blocks.length >> 6; // available longs * 64 (bits per long) / 4096 (blocks per section) (floored result)
}
public int getSectionY() {
return sectionY;
}
public BlockState getBlockState(int x, int y, int z) {
if (palette.length == 1) return palette[0];
if (blocks.length == 0) return BlockState.AIR;
x &= 0xF; y &= 0xF; z &= 0xF; // Math.floorMod(pos.getX(), 16)
int blockIndex = y * 256 + z * 16 + x;
long value = MCAMath.getValueFromLongStream(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(int x, int y, int z, LightData target) {
if (blockLight.length == 0 && skyLight.length == 0) return target.set(0, 0);
x &= 0xF; y &= 0xF; z &= 0xF; // Math.floorMod(pos.getX(), 16)
int blockByteIndex = y * 256 + z * 16 + x;
int blockHalfByteIndex = blockByteIndex >> 1; // blockByteIndex / 2
boolean largeHalf = (blockByteIndex & 0x1) != 0; // (blockByteIndex % 2) == 0
return target.set(
this.skyLight.length > 0 ? MCAMath.getByteHalf(this.skyLight[blockHalfByteIndex], largeHalf) : 0,
this.blockLight.length > 0 ? MCAMath.getByteHalf(this.blockLight[blockHalfByteIndex], largeHalf) : 0
);
}
}
*/
}

View File

@ -1,239 +0,0 @@
/*
* 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 de.bluecolored.bluemap.core.logger.Logger;
import de.bluecolored.bluemap.core.mca.data.ChunkData;
import de.bluecolored.bluemap.core.mca.data.HeightmapsData;
import de.bluecolored.bluemap.core.mca.data.SectionData;
import de.bluecolored.bluemap.core.world.Biome;
import de.bluecolored.bluemap.core.world.BlockState;
import de.bluecolored.bluemap.core.world.LightData;
@SuppressWarnings("FieldMayBeFinal")
public class ChunkAnvil116 extends MCAChunk {
private final boolean isGenerated;
private final boolean hasLight;
private final long inhabitedTime;
private final int sectionMin, sectionMax;
private final Section[] sections;
private final int[] biomes;
private final long[] oceanFloorHeights;
private final long[] worldSurfaceHeights;
public ChunkAnvil116(MCAWorld world, ChunkData chunkData) {
super(world, chunkData);
String status = chunkData.getStatus();
boolean generated = status.equals("full");
this.hasLight = generated;
if (!generated && getWorld().isIgnoreMissingLightData())
generated = !status.equals("empty");
this.isGenerated = generated;
this.inhabitedTime = chunkData.getInhabitedTime();
HeightmapsData heightmapsData = chunkData.getHeightmaps();
this.worldSurfaceHeights = heightmapsData.getWorldSurface();
this.oceanFloorHeights = heightmapsData.getOceanFloor();
SectionData[] sectionDatas = chunkData.getSections();
if (sectionDatas != null && sectionDatas.length > 0) {
int min = Integer.MAX_VALUE;
int max = Integer.MIN_VALUE;
// find section min/max y
for (SectionData sectionData : sectionDatas) {
int y = sectionData.getY();
if (min > y) min = y;
if (max < y) max = y;
}
// load sections into ordered array
this.sections = new Section[1 + max - min];
for (SectionData sectionData : sectionDatas) {
Section section = new Section(sectionData);
int y = section.getSectionY();
if (min > y) min = y;
if (max < y) max = y;
sections[section.sectionY - min] = section;
}
this.sectionMin = min;
this.sectionMax = max;
} else {
this.sections = new Section[0];
this.sectionMin = 0;
this.sectionMax = 0;
}
this.biomes = chunkData.getBiomes();
}
@Override
public boolean isGenerated() {
return isGenerated;
}
@Override
public long getInhabitedTime() {
return inhabitedTime;
}
@Override
public BlockState getBlockState(int x, int y, int z) {
int sectionY = y >> 4;
Section section = getSection(sectionY);
if (section == null) return BlockState.AIR;
return section.getBlockState(x, y, z);
}
@Override
public LightData getLightData(int x, int y, int z, LightData target) {
if (!hasLight) return target.set(getWorld().getSkyLight(), 0);
int sectionY = y >> 4;
Section section = getSection(sectionY);
if (section == null) return (sectionY < sectionMin) ? target.set(0, 0) : target.set(getWorld().getSkyLight(), 0);
return section.getLightData(x, y, z, target);
}
@Override
public String getBiome(int x, int y, int z) {
if (biomes.length < 16) return Biome.DEFAULT.getFormatted();
x = (x & 0xF) / 4; // Math.floorMod(pos.getX(), 16)
z = (z & 0xF) / 4;
y = y / 4;
int biomeIntIndex = y * 16 + z * 4 + x; // TODO: fix this for 1.17+ worlds with negative y?
// shift y up/down if not in range
if (biomeIntIndex >= biomes.length) biomeIntIndex -= (((biomeIntIndex - biomes.length) >> 4) + 1) * 16;
if (biomeIntIndex < 0) biomeIntIndex -= (biomeIntIndex >> 4) * 16;
return LegacyBiomes.idFor(biomes[biomeIntIndex]);
}
@Override
public int getMinY(int x, int z) {
return sectionMin * 16;
}
@Override
public int getMaxY(int x, int z) {
return sectionMax * 16 + 15;
}
@Override
public int getWorldSurfaceY(int x, int z) {
if (this.worldSurfaceHeights.length < 37) return 0;
x &= 0xF; z &= 0xF;
return (int) MCAMath.getValueFromLongArray(this.worldSurfaceHeights, z * 16 + x, 9);
}
@Override
public int getOceanFloorY(int x, int z) {
if (this.oceanFloorHeights.length < 37) return 0;
x &= 0xF; z &= 0xF;
return (int) MCAMath.getValueFromLongArray(this.oceanFloorHeights, z * 16 + x, 9);
}
private Section getSection(int y) {
y -= sectionMin;
if (y < 0 || y >= this.sections.length) return null;
return this.sections[y];
}
private static class Section {
private final int sectionY;
private final byte[] blockLight;
private final byte[] skyLight;
private final long[] blocks;
private final BlockState[] palette;
private final int bitsPerBlock;
public Section(SectionData sectionData) {
this.sectionY = sectionData.getY();
this.blockLight = sectionData.getBlockLight();
this.skyLight = sectionData.getSkyLight();
this.blocks = sectionData.getBlockStatesData();
this.palette = sectionData.getPalette();
this.bitsPerBlock = this.blocks.length >> 6; // available longs * 64 (bits per long) / 4096 (blocks per section) (floored result)
}
public int getSectionY() {
return sectionY;
}
public BlockState getBlockState(int x, int y, int z) {
if (palette.length == 1) return palette[0];
if (blocks.length == 0) return BlockState.AIR;
x &= 0xF; y &= 0xF; z &= 0xF; // Math.floorMod(pos.getX(), 16)
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(int x, int y, int z, LightData target) {
if (blockLight.length == 0 && skyLight.length == 0) return target.set(0, 0);
x &= 0xF; y &= 0xF; z &= 0xF; // Math.floorMod(pos.getX(), 16)
int blockByteIndex = y * 256 + z * 16 + x;
int blockHalfByteIndex = blockByteIndex >> 1; // blockByteIndex / 2
boolean largeHalf = (blockByteIndex & 0x1) != 0; // (blockByteIndex % 2) == 0
return target.set(
this.skyLight.length > 0 ? MCAMath.getByteHalf(this.skyLight[blockHalfByteIndex], largeHalf) : 0,
this.blockLight.length > 0 ? MCAMath.getByteHalf(this.blockLight[blockHalfByteIndex], largeHalf) : 0
);
}
}
}

View File

@ -1,247 +0,0 @@
/*
* 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 de.bluecolored.bluemap.core.logger.Logger;
import de.bluecolored.bluemap.core.mca.data.*;
import de.bluecolored.bluemap.core.world.Biome;
import de.bluecolored.bluemap.core.world.BlockState;
import de.bluecolored.bluemap.core.world.LightData;
public class ChunkAnvil118 extends MCAChunk {
private final boolean isGenerated;
private final boolean hasLight;
private final long inhabitedTime;
private final int sectionMin, sectionMax;
private final Section[] sections;
private final long[] oceanFloorHeights;
private final long[] worldSurfaceHeights;
public ChunkAnvil118(MCAWorld world, ChunkData chunkData) {
super(world, chunkData);
String status = chunkData.getStatus();
boolean generated = status.equals("minecraft:full") || status.equals("full");
this.hasLight = generated;
if (!generated && getWorld().isIgnoreMissingLightData())
generated = !status.equals("empty") && !status.equals("minecraft:empty");
this.isGenerated = generated;
this.inhabitedTime = chunkData.getInhabitedTime();
HeightmapsData heightmapsData = chunkData.getHeightmaps();
this.worldSurfaceHeights = heightmapsData.getWorldSurface();
this.oceanFloorHeights = heightmapsData.getOceanFloor();
SectionData[] sectionDatas = chunkData.getSections();
if (sectionDatas != null && sectionDatas.length > 0) {
int min = Integer.MAX_VALUE;
int max = Integer.MIN_VALUE;
// find section min/max y
for (SectionData sectionData : sectionDatas) {
int y = sectionData.getY();
if (min > y) min = y;
if (max < y) max = y;
}
// load sections into ordered array
this.sections = new Section[1 + max - min];
for (SectionData sectionData : sectionDatas) {
Section section = new Section(sectionData);
int y = section.getSectionY();
if (min > y) min = y;
if (max < y) max = y;
sections[section.sectionY - min] = section;
}
this.sectionMin = min;
this.sectionMax = max;
} else {
this.sections = new Section[0];
this.sectionMin = 0;
this.sectionMax = 0;
}
}
@Override
public boolean isGenerated() {
return isGenerated;
}
@Override
public long getInhabitedTime() {
return inhabitedTime;
}
@Override
public BlockState getBlockState(int x, int y, int z) {
int sectionY = y >> 4;
Section section = getSection(sectionY);
if (section == null) return BlockState.AIR;
return section.getBlockState(x, y, z);
}
@Override
public LightData getLightData(int x, int y, int z, LightData target) {
if (!hasLight) return target.set(getWorld().getSkyLight(), 0);
int sectionY = y >> 4;
Section section = getSection(sectionY);
if (section == null) return (sectionY < sectionMin) ? target.set(0, 0) : target.set(getWorld().getSkyLight(), 0);
return section.getLightData(x, y, z, target);
}
@Override
public String getBiome(int x, int y, int z) {
int sectionY = y >> 4;
Section section = getSection(sectionY);
if (section == null) return Biome.DEFAULT.getFormatted();
return section.getBiome(x, y, z);
}
@Override
public int getMinY(int x, int z) {
return sectionMin * 16;
}
@Override
public int getMaxY(int x, int z) {
return sectionMax * 16 + 15;
}
@Override
public int getWorldSurfaceY(int x, int z) {
if (this.worldSurfaceHeights.length < 37) return 0;
x &= 0xF; z &= 0xF;
return (int) MCAMath.getValueFromLongArray(this.worldSurfaceHeights, z * 16 + x, 9) - 64;
}
@Override
public int getOceanFloorY(int x, int z) {
if (this.oceanFloorHeights.length < 37) return 0;
x &= 0xF; z &= 0xF;
return (int) MCAMath.getValueFromLongArray(this.oceanFloorHeights, z * 16 + x, 9) - 64;
}
private Section getSection(int y) {
y -= sectionMin;
if (y < 0 || y >= this.sections.length) return null;
return this.sections[y];
}
private static class Section {
private final int sectionY;
private final byte[] blockLight;
private final byte[] skyLight;
private final long[] blocks;
private final long[] biomes;
private final BlockState[] blockPalette;
private final String[] biomePalette;
private final int bitsPerBlock, bitsPerBiome;
public Section(SectionData sectionData) {
this.sectionY = sectionData.getY();
this.blockLight = sectionData.getBlockLight();
this.skyLight = sectionData.getSkyLight();
BlockStatesData blockStates = sectionData.getBlockStates();
this.blocks = blockStates.getData();
this.blockPalette = blockStates.getPalette();
BiomesData biomesData = sectionData.getBiomes();
this.biomes = biomesData.getData();
this.biomePalette = biomesData.getPalette();
this.bitsPerBlock = this.blocks.length >> 6; // available longs * 64 (bits per long) / 4096 (blocks per section) (floored result)
this.bitsPerBiome = MCAMath.ceilLog2(this.biomePalette.length);
}
public int getSectionY() {
return sectionY;
}
public BlockState getBlockState(int x, int y, int z) {
if (blockPalette.length == 1) return blockPalette[0];
if (blocks.length == 0) return BlockState.AIR;
int blockIndex = (y & 0xF) << 8 | (z & 0xF) << 4 | x & 0xF;
long value = MCAMath.getValueFromLongArray(blocks, blockIndex, bitsPerBlock);
if (value >= blockPalette.length) {
Logger.global.noFloodWarning("palettewarning", "Got block-palette value " + value + " but palette has size of " + blockPalette.length + "! (Future occasions of this error will not be logged)");
return BlockState.MISSING;
}
return blockPalette[(int) value];
}
public LightData getLightData(int x, int y, int z, LightData target) {
if (blockLight.length == 0 && skyLight.length == 0) return target.set(0, 0);
int blockByteIndex = (y & 0xF) << 8 | (z & 0xF) << 4 | x & 0xF;
int blockHalfByteIndex = blockByteIndex >> 1; // blockByteIndex / 2
boolean largeHalf = (blockByteIndex & 0x1) != 0; // (blockByteIndex % 2) == 0
return target.set(
this.skyLight.length > 0 ? MCAMath.getByteHalf(this.skyLight[blockHalfByteIndex], largeHalf) : 0,
this.blockLight.length > 0 ? MCAMath.getByteHalf(this.blockLight[blockHalfByteIndex], largeHalf) : 0
);
}
public String getBiome(int x, int y, int z) {
if (biomePalette.length == 0) return Biome.DEFAULT.getValue();
if (biomePalette.length == 1 || biomes.length == 0) return biomePalette[0];
int biomeIndex = (y & 0b1100) << 2 | z & 0b1100 | (x & 0b1100) >> 2;
long value = MCAMath.getValueFromLongArray(biomes, biomeIndex, bitsPerBiome);
if (value >= biomePalette.length) {
Logger.global.noFloodWarning("biomepalettewarning", "Got biome-palette value " + value + " but palette has size of " + biomePalette.length + "! (Future occasions of this error will not be logged)");
return Biome.DEFAULT.getValue();
}
return biomePalette[(int) value];
}
}
private static PackedIntArrayAccess heightmap(int worldHeight, long[] data) {
return new PackedIntArrayAccess(MCAMath.ceilLog2(worldHeight + 1), data);
}
}

View File

@ -1,116 +0,0 @@
/*
* 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;
public class LegacyBiomes {
private static final String[] BIOME_IDS = new String[170];
static {
Arrays.fill(BIOME_IDS, "minecraft:ocean");
BIOME_IDS[0] = "minecraft:ocean";
BIOME_IDS[1] = "minecraft:plains";
BIOME_IDS[2] = "minecraft:desert";
BIOME_IDS[3] = "minecraft:mountains";
BIOME_IDS[4] = "minecraft:forest";
BIOME_IDS[5] = "minecraft:taiga";
BIOME_IDS[6] = "minecraft:swamp";
BIOME_IDS[7] = "minecraft:river";
BIOME_IDS[8] = "minecraft:nether";
BIOME_IDS[9] = "minecraft:the_end";
BIOME_IDS[10] = "minecraft:frozen_ocean";
BIOME_IDS[11] = "minecraft:frozen_river";
BIOME_IDS[12] = "minecraft:snowy_tundra";
BIOME_IDS[13] = "minecraft:snowy_mountains";
BIOME_IDS[14] = "minecraft:mushroom_fields";
BIOME_IDS[15] = "minecraft:mushroom_field_shore";
BIOME_IDS[16] = "minecraft:beach";
BIOME_IDS[17] = "minecraft:desert_hills";
BIOME_IDS[18] = "minecraft:wooded_hills";
BIOME_IDS[19] = "minecraft:taiga_hills";
BIOME_IDS[20] = "minecraft:mountain_edge";
BIOME_IDS[21] = "minecraft:jungle";
BIOME_IDS[22] = "minecraft:jungle_hills";
BIOME_IDS[23] = "minecraft:jungle_edge";
BIOME_IDS[24] = "minecraft:deep_ocean";
BIOME_IDS[25] = "minecraft:stone_shore";
BIOME_IDS[26] = "minecraft:snowy_beach";
BIOME_IDS[27] = "minecraft:birch_forest";
BIOME_IDS[28] = "minecraft:birch_forest_hills";
BIOME_IDS[29] = "minecraft:dark_forest";
BIOME_IDS[30] = "minecraft:snowy_taiga";
BIOME_IDS[31] = "minecraft:snowy_taiga_hills";
BIOME_IDS[32] = "minecraft:giant_tree_taiga";
BIOME_IDS[33] = "minecraft:giant_tree_taiga_hills";
BIOME_IDS[34] = "minecraft:wooded_mountains";
BIOME_IDS[35] = "minecraft:savanna";
BIOME_IDS[36] = "minecraft:savanna_plateau";
BIOME_IDS[37] = "minecraft:badlands";
BIOME_IDS[38] = "minecraft:wooded_badlands_plateau";
BIOME_IDS[39] = "minecraft:badlands_plateau";
BIOME_IDS[40] = "minecraft:small_end_islands";
BIOME_IDS[41] = "minecraft:end_midlands";
BIOME_IDS[42] = "minecraft:end_highlands";
BIOME_IDS[43] = "minecraft:end_barrens";
BIOME_IDS[44] = "minecraft:warm_ocean";
BIOME_IDS[45] = "minecraft:lukewarm_ocean";
BIOME_IDS[46] = "minecraft:cold_ocean";
BIOME_IDS[47] = "minecraft:deep_warm_ocean";
BIOME_IDS[48] = "minecraft:deep_lukewarm_ocean";
BIOME_IDS[49] = "minecraft:deep_cold_ocean";
BIOME_IDS[50] = "minecraft:deep_frozen_ocean";
BIOME_IDS[127] = "minecraft:the_void";
BIOME_IDS[129] = "minecraft:sunflower_plains";
BIOME_IDS[130] = "minecraft:desert_lakes";
BIOME_IDS[131] = "minecraft:gravelly_mountains";
BIOME_IDS[132] = "minecraft:flower_forest";
BIOME_IDS[133] = "minecraft:taiga_mountains";
BIOME_IDS[134] = "minecraft:swamp_hills";
BIOME_IDS[140] = "minecraft:ice_spikes";
BIOME_IDS[149] = "minecraft:modified_jungle";
BIOME_IDS[151] = "minecraft:modified_jungle_edge";
BIOME_IDS[155] = "minecraft:tall_birch_forest";
BIOME_IDS[156] = "minecraft:tall_birch_hills";
BIOME_IDS[157] = "minecraft:dark_forest_hills";
BIOME_IDS[158] = "minecraft:snowy_taiga_mountains";
BIOME_IDS[160] = "minecraft:giant_spruce_taiga";
BIOME_IDS[161] = "minecraft:giant_spruce_taiga_hills";
BIOME_IDS[162] = "minecraft:modified_gravelly_mountains";
BIOME_IDS[163] = "minecraft:shattered_savanna";
BIOME_IDS[164] = "minecraft:shattered_savanna_plateau";
BIOME_IDS[165] = "minecraft:eroded_badlands";
BIOME_IDS[166] = "minecraft:modified_wooded_badlands_plateau";
BIOME_IDS[167] = "minecraft:modified_badlands_plateau";
BIOME_IDS[168] = "minecraft:bamboo_jungle";
BIOME_IDS[169] = "minecraft:bamboo_jungle_hills";
}
public static String idFor(int legacyId) {
if (legacyId < 0 || legacyId >= BIOME_IDS.length) legacyId = 0;
return BIOME_IDS[legacyId];
}
}

View File

@ -1,114 +0,0 @@
/*
* 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 de.bluecolored.bluemap.core.mca.data.ChunkData;
import de.bluecolored.bluemap.core.world.BlockState;
import de.bluecolored.bluemap.core.world.Chunk;
import de.bluecolored.bluemap.core.world.LightData;
import java.io.IOException;
public abstract class MCAChunk implements Chunk {
private final MCAWorld world;
private final int dataVersion;
protected MCAChunk() {
this.world = null;
this.dataVersion = -1;
}
protected MCAChunk(MCAWorld world) {
this.world = world;
this.dataVersion = -1;
}
protected MCAChunk(MCAWorld world, ChunkData chunkData) {
this.world = world;
dataVersion = chunkData.getDataVersion();
}
@Override
public abstract boolean isGenerated();
public int getDataVersion() {
return dataVersion;
}
@Override
public abstract long getInhabitedTime();
@Override
public abstract BlockState getBlockState(int x, int y, int z);
@Override
public abstract LightData getLightData(int x, int y, int z, LightData target);
@Override
public abstract String getBiome(int x, int y, int z);
@Override
public int getMaxY(int x, int z) {
return 255;
}
@Override
public int getMinY(int x, int z) {
return 0;
}
@Override
public int getWorldSurfaceY(int x, int z) { return 0; }
@Override
public int getOceanFloorY(int x, int z) { return 0; }
protected MCAWorld getWorld() {
return world;
}
public static MCAChunk create(MCAWorld world, ChunkData chunkData) throws IOException {
int version = chunkData.getDataVersion();
/*
if (version < 2200) return new ChunkAnvil113(world, chunkData);
if (version < 2500) return new ChunkAnvil115(world, chunkData);
*/
if (version < 2844) return new ChunkAnvil116(world, chunkData);
return new ChunkAnvil118(world, chunkData);
}
@Override
public String toString() {
return "MCAChunk{" +
"world=" + world +
"dataVersion=" + dataVersion +
"isGenerated()=" + isGenerated() +
'}';
}
}

View File

@ -1,285 +0,0 @@
/*
* 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 com.flowpowered.math.vector.Vector2i;
import com.flowpowered.math.vector.Vector3i;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import de.bluecolored.bluemap.api.debug.DebugDump;
import de.bluecolored.bluemap.core.BlueMap;
import de.bluecolored.bluemap.core.logger.Logger;
import de.bluecolored.bluemap.core.mca.data.LevelData;
import de.bluecolored.bluemap.core.mca.region.RegionType;
import de.bluecolored.bluemap.core.util.Vector2iCache;
import de.bluecolored.bluemap.core.world.*;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.zip.GZIPInputStream;
@DebugDump
public class MCAWorld implements World {
private static final Grid CHUNK_GRID = new Grid(16);
private static final Grid REGION_GRID = new Grid(32).multiply(CHUNK_GRID);
private static final Vector2iCache VECTOR_2_I_CACHE = new Vector2iCache();
private final Path worldFolder;
private final String name;
private final Vector3i spawnPoint;
private final int skyLight;
private final boolean ignoreMissingLightData;
private final LoadingCache<Vector2i, Region> regionCache;
private final LoadingCache<Vector2i, Chunk> chunkCache;
public MCAWorld(Path worldFolder, int skyLight, boolean ignoreMissingLightData) throws IOException {
this.worldFolder = worldFolder.toRealPath();
this.skyLight = skyLight;
this.ignoreMissingLightData = ignoreMissingLightData;
this.regionCache = Caffeine.newBuilder()
.executor(BlueMap.THREAD_POOL)
.maximumSize(100)
.expireAfterWrite(1, TimeUnit.MINUTES)
.build(this::loadRegion);
this.chunkCache = Caffeine.newBuilder()
.executor(BlueMap.THREAD_POOL)
.maximumSize(500)
.expireAfterWrite(1, TimeUnit.MINUTES)
.build(this::loadChunk);
Path levelFile = resolveLevelFile(worldFolder);
try (InputStream in = new GZIPInputStream(new BufferedInputStream(Files.newInputStream(levelFile)))) {
LevelData level = MCAMath.BLUENBT.read(in, LevelData.class);
LevelData.Data levelData = level.getData();
this.name = levelData.getLevelName();
this.spawnPoint = new Vector3i(
levelData.getSpawnX(),
levelData.getSpawnY(),
levelData.getSpawnZ()
);
} catch (IOException ex) {
throw new IOException("Failed to read level.dat!", ex);
}
}
@Override
public Chunk getChunkAtBlock(int x, int y, int z) {
return getChunk(x >> 4, z >> 4);
}
@Override
public Chunk getChunk(int x, int z) {
return getChunk(VECTOR_2_I_CACHE.get(x, z));
}
private Chunk getChunk(Vector2i pos) {
return chunkCache.get(pos);
}
@Override
public Region getRegion(int x, int z) {
return getRegion(VECTOR_2_I_CACHE.get(x, z));
}
private Region getRegion(Vector2i pos) {
return regionCache.get(pos);
}
@Override
public Collection<Vector2i> listRegions() {
File[] regionFiles = getRegionFolder().toFile().listFiles();
if (regionFiles == null) return Collections.emptyList();
List<Vector2i> regions = new ArrayList<>(regionFiles.length);
for (File file : regionFiles) {
if (RegionType.forFileName(file.getName()) == null) continue;
if (file.length() <= 0) continue;
try {
String[] filenameParts = file.getName().split("\\.");
int rX = Integer.parseInt(filenameParts[1]);
int rZ = Integer.parseInt(filenameParts[2]);
regions.add(new Vector2i(rX, rZ));
} catch (NumberFormatException ignore) {}
}
return regions;
}
@Override
public String getName() {
return name;
}
@Override
public Path getSaveFolder() {
return worldFolder;
}
@Override
public int getSkyLight() {
return skyLight;
}
@Override
public int getMinY(int x, int z) {
return getChunk(x >> 4, z >> 4).getMinY(x, z);
}
@Override
public int getMaxY(int x, int z) {
return getChunk(x >> 4, z >> 4).getMaxY(x, z);
}
@Override
public Grid getChunkGrid() {
return CHUNK_GRID;
}
@Override
public Grid getRegionGrid() {
return REGION_GRID;
}
@Override
public Vector3i getSpawnPoint() {
return spawnPoint;
}
@Override
public void invalidateChunkCache() {
chunkCache.invalidateAll();
}
@Override
public void invalidateChunkCache(int x, int z) {
chunkCache.invalidate(VECTOR_2_I_CACHE.get(x, z));
}
@Override
public void cleanUpChunkCache() {
chunkCache.cleanUp();
}
public Path getWorldFolder() {
return worldFolder;
}
private Path getRegionFolder() {
return worldFolder.resolve("region");
}
public boolean isIgnoreMissingLightData() {
return ignoreMissingLightData;
}
private Region loadRegion(Vector2i regionPos) {
return loadRegion(regionPos.getX(), regionPos.getY());
}
Region loadRegion(int x, int z) {
return RegionType.loadRegion(this, getRegionFolder(), x, z);
}
private Chunk loadChunk(Vector2i chunkPos) {
return loadChunk(chunkPos.getX(), chunkPos.getY());
}
Chunk loadChunk(int x, int z) {
final int tries = 3;
final int tryInterval = 1000;
Exception loadException = null;
for (int i = 0; i < tries; i++) {
try {
return getRegion(x >> 5, z >> 5)
.loadChunk(x, z, ignoreMissingLightData);
} catch (IOException | RuntimeException e) {
if (loadException != null) e.addSuppressed(loadException);
loadException = e;
if (i + 1 < tries) {
try {
Thread.sleep(tryInterval);
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
break;
}
}
}
}
Logger.global.logDebug("Unexpected exception trying to load chunk (x:" + x + ", z:" + z + "):" + loadException);
return EmptyChunk.INSTANCE;
}
@Override
public String toString() {
return "MCAWorld{" +
"worldFolder=" + worldFolder +
", name='" + name + '\'' +
", spawnPoint=" + spawnPoint +
", skyLight=" + skyLight +
", ignoreMissingLightData=" + ignoreMissingLightData +
'}';
}
private static Path resolveLevelFile(Path worldFolder) throws IOException {
Path levelFolder = worldFolder.toRealPath();
Path levelFile = levelFolder.resolve("level.dat");
int searchDepth = 0;
while (!Files.isRegularFile(levelFile) && searchDepth < 4) {
searchDepth++;
levelFolder = levelFolder.getParent();
if (levelFolder == null) break;
levelFile = levelFolder.resolve("level.dat");
}
if (!Files.isRegularFile(levelFile))
throw new FileNotFoundException("Could not find a level.dat file for this world!");
return levelFile;
}
}

View File

@ -1,14 +0,0 @@
package de.bluecolored.bluemap.core.mca.data;
import lombok.Getter;
@Getter
@SuppressWarnings("FieldMayBeFinal")
public class BiomesData {
private static final String[] EMPTY_STRING_ARRAY = new String[0];
private static final long[] EMPTY_LONG_ARRAY = new long[0];
private String[] palette = EMPTY_STRING_ARRAY;
private long[] data = EMPTY_LONG_ARRAY;
}

View File

@ -1,15 +0,0 @@
package de.bluecolored.bluemap.core.mca.data;
import de.bluecolored.bluemap.core.world.BlockState;
import lombok.Getter;
@Getter
@SuppressWarnings("FieldMayBeFinal")
public class BlockStatesData {
private static final long[] EMPTY_LONG_ARRAY = new long[0];
private static final BlockState[] EMPTY_BLOCKSTATE_ARRAY = new BlockState[0];
private BlockState[] palette = EMPTY_BLOCKSTATE_ARRAY;
private long[] data = EMPTY_LONG_ARRAY;
}

View File

@ -1,20 +0,0 @@
package de.bluecolored.bluemap.core.mca.data;
import lombok.Getter;
import org.jetbrains.annotations.Nullable;
@Getter
@SuppressWarnings("FieldMayBeFinal")
public class ChunkData {
private static final HeightmapsData EMPTY_HEIGHTMAPS_DATA = new HeightmapsData();
private int dataVersion = 0;
private String status = "none";
private long inhabitedTime = 0;
private HeightmapsData heightmaps = EMPTY_HEIGHTMAPS_DATA;
private @Nullable SectionData[] sections = null;
// <= 1.16
private int[] biomes;
}

View File

@ -1,17 +0,0 @@
package de.bluecolored.bluemap.core.mca.data;
import de.bluecolored.bluenbt.NBTName;
import lombok.Getter;
@Getter
@SuppressWarnings("FieldMayBeFinal")
public class HeightmapsData {
private static final long[] EMPTY_LONG_ARRAY = new long[0];
@NBTName("WORLD_SURFACE")
private long[] worldSurface = EMPTY_LONG_ARRAY;
@NBTName("OCEAN_FLOOR")
private long[] oceanFloor = EMPTY_LONG_ARRAY;
}

View File

@ -1,19 +0,0 @@
package de.bluecolored.bluemap.core.mca.data;
import lombok.Getter;
@Getter
@SuppressWarnings("FieldMayBeFinal")
public class LevelData {
private Data data = new Data();
@Getter
public static class Data {
private String levelName = "world";
private int spawnX = 0, spawnY = 0, spawnZ = 0;
}
}

View File

@ -1,28 +0,0 @@
package de.bluecolored.bluemap.core.mca.data;
import de.bluecolored.bluemap.core.world.BlockState;
import de.bluecolored.bluenbt.NBTName;
import lombok.Getter;
@Getter
@SuppressWarnings("FieldMayBeFinal")
public class SectionData {
private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
private static final long[] EMPTY_LONG_ARRAY = new long[0];
private static final BlockStatesData EMPTY_BLOCKSTATESDATA = new BlockStatesData();
private static final BlockState[] EMPTY_BLOCKSTATE_ARRAY = new BlockState[0];
private static final BiomesData EMPTY_BIOMESDATA = new BiomesData();
private int y = 0;
private byte[] blockLight = EMPTY_BYTE_ARRAY;
private byte[] skyLight = EMPTY_BYTE_ARRAY;
@NBTName("block_states")
private BlockStatesData blockStates = EMPTY_BLOCKSTATESDATA;
private BiomesData biomes = EMPTY_BIOMESDATA;
// <= 1.16
@NBTName("BlockStates")
private long[] blockStatesData = EMPTY_LONG_ARRAY;
private BlockState[] palette = EMPTY_BLOCKSTATE_ARRAY;
}

View File

@ -1,209 +0,0 @@
/*
* 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.region;
import com.flowpowered.math.vector.Vector2i;
import de.bluecolored.bluemap.core.logger.Logger;
import de.bluecolored.bluemap.core.mca.MCAChunk;
import de.bluecolored.bluemap.core.mca.MCAMath;
import de.bluecolored.bluemap.core.mca.MCAWorld;
import de.bluecolored.bluemap.core.mca.data.ChunkData;
import de.bluecolored.bluemap.core.world.Chunk;
import de.bluecolored.bluemap.core.world.EmptyChunk;
import de.bluecolored.bluemap.core.world.Region;
import io.airlift.compress.zstd.ZstdInputStream;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
public class LinearRegion implements Region {
public static final String FILE_SUFFIX = ".linear";
private static final List<Byte> SUPPORTED_VERSIONS = Arrays.asList((byte) 1, (byte) 2);
private static final long SUPERBLOCK = -4323716122432332390L;
private static final int HEADER_SIZE = 32;
private static final int FOOTER_SIZE = 8;
private final MCAWorld world;
private final Path regionFile;
private final Vector2i regionPos;
public LinearRegion(MCAWorld world, Path regionFile) throws IllegalArgumentException {
this.world = world;
this.regionFile = regionFile;
String[] filenameParts = regionFile.getFileName().toString().split("\\.");
int rX = Integer.parseInt(filenameParts[1]);
int rZ = Integer.parseInt(filenameParts[2]);
this.regionPos = new Vector2i(rX, rZ);
}
@Override
public Chunk loadChunk(int chunkX, int chunkZ, boolean ignoreMissingLightData) throws IOException {
if (Files.notExists(regionFile)) return EmptyChunk.INSTANCE;
long fileLength = Files.size(regionFile);
if (fileLength == 0) return EmptyChunk.INSTANCE;
try (InputStream inputStream = Files.newInputStream(regionFile);
DataInputStream rawDataStream = new DataInputStream(inputStream)) {
long superBlock = rawDataStream.readLong();
if (superBlock != SUPERBLOCK)
throw new RuntimeException("Invalid superblock: " + superBlock + " file " + regionFile);
byte version = rawDataStream.readByte();
if (!SUPPORTED_VERSIONS.contains(version))
throw new RuntimeException("Invalid version: " + version + " file " + regionFile);
// Skip newestTimestamp (Long) + Compression level (Byte) + Chunk count (Short): Unused.
rawDataStream.skipBytes(11);
int dataCount = rawDataStream.readInt();
if (fileLength != HEADER_SIZE + dataCount + FOOTER_SIZE)
throw new RuntimeException("Invalid file length: " + this.regionFile + " " + fileLength + " " + (HEADER_SIZE + dataCount + FOOTER_SIZE));
// Skip data hash (Long): Unused.
rawDataStream.skipBytes(8);
byte[] rawCompressed = new byte[dataCount];
rawDataStream.readFully(rawCompressed, 0, dataCount);
superBlock = rawDataStream.readLong();
if (superBlock != SUPERBLOCK)
throw new RuntimeException("Invalid footer superblock: " + this.regionFile);
try (DataInputStream dis = new DataInputStream(new ZstdInputStream(new ByteArrayInputStream(rawCompressed)))) {
int x = chunkX - (regionPos.getX() << 5);
int z = chunkZ - (regionPos.getY() << 5);
int pos = (z << 5) + x;
int skip = 0;
for (int i = 0; i < pos; i++) {
skip += dis.readInt(); // Size of the chunk (bytes) to skip
dis.skipBytes(4); // Skip timestamps
}
int size = dis.readInt();
if (size <= 0) return EmptyChunk.INSTANCE;
dis.skipBytes(((1024 - pos - 1) << 3) + 4); // Skip current chunk 0 and unneeded other chunks zero/size
dis.skipBytes(skip); // Skip unneeded chunks data
ChunkData chunkData = MCAMath.BLUENBT.read(dis, ChunkData.class);
return MCAChunk.create(world, chunkData);
}
} catch (RuntimeException e) {
throw new IOException(e);
}
}
@Override
public Collection<Vector2i> listChunks(long modifiedSince) {
if (Files.notExists(regionFile)) return Collections.emptyList();
long fileLength;
try {
fileLength = Files.size(regionFile);
if (fileLength == 0) return Collections.emptyList();
} catch (IOException ex) {
Logger.global.logWarning("Failed to read file-size for file: " + regionFile);
return Collections.emptyList();
}
List<Vector2i> chunks = new ArrayList<>(1024); //1024 = 32 x 32 chunks per region-file
try (InputStream inputStream = Files.newInputStream(regionFile);
DataInputStream rawDataStream = new DataInputStream(inputStream)) {
long superBlock = rawDataStream.readLong();
if (superBlock != SUPERBLOCK)
throw new RuntimeException("Invalid superblock: " + superBlock + " file " + regionFile);
byte version = rawDataStream.readByte();
if (!SUPPORTED_VERSIONS.contains(version))
throw new RuntimeException("Invalid version: " + version + " file " + regionFile);
int date = (int) (modifiedSince / 1000);
// If whole region is the same - skip.
long newestTimestamp = rawDataStream.readLong();
if (newestTimestamp < date) return Collections.emptyList();
// Linear v1 files store whole region timestamp, not chunk timestamp. We need to render the whole region file.
if (version == 1) {
for(int i = 0 ; i < 1024; i++)
chunks.add(new Vector2i((regionPos.getX() << 5) + (i & 31), (regionPos.getY() << 5) + (i >> 5)));
return chunks;
}
// Linear v2: Chunk timestamps are here!
// Skip Compression level (Byte) + Chunk count (Short): Unused.
rawDataStream.skipBytes(3);
int dataCount = rawDataStream.readInt();
if (fileLength != HEADER_SIZE + dataCount + FOOTER_SIZE)
throw new RuntimeException("Invalid file length: " + this.regionFile + " " + fileLength + " " + (HEADER_SIZE + dataCount + FOOTER_SIZE));
// Skip data hash (Long): Unused.
rawDataStream.skipBytes(8);
byte[] rawCompressed = new byte[dataCount];
rawDataStream.readFully(rawCompressed, 0, dataCount);
superBlock = rawDataStream.readLong();
if (superBlock != SUPERBLOCK)
throw new RuntimeException("Invalid footer SuperBlock: " + this.regionFile);
try (DataInputStream dis = new DataInputStream(new ZstdInputStream(new ByteArrayInputStream(rawCompressed)))) {
for (int i = 0; i < 1024; i++) {
dis.skipBytes(4); // Skip size of the chunk
int timestamp = dis.readInt();
if (timestamp >= date) // Timestamps
chunks.add(new Vector2i((regionPos.getX() << 5) + (i & 31), (regionPos.getY() << 5) + (i >> 5)));
}
}
} catch (RuntimeException | IOException ex) {
Logger.global.logWarning("Failed to read .linear file: " + regionFile + " (" + ex + ")");
}
return chunks;
}
@Override
public Path getRegionFile() {
return regionFile;
}
public static String getRegionFileName(int regionX, int regionZ) {
return "r." + regionX + "." + regionZ + FILE_SUFFIX;
}
}

View File

@ -1,162 +0,0 @@
/*
* 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.region;
import com.flowpowered.math.vector.Vector2i;
import de.bluecolored.bluemap.core.logger.Logger;
import de.bluecolored.bluemap.core.mca.MCAChunk;
import de.bluecolored.bluemap.core.mca.MCAMath;
import de.bluecolored.bluemap.core.mca.MCAWorld;
import de.bluecolored.bluemap.core.mca.data.ChunkData;
import de.bluecolored.bluemap.core.storage.Compression;
import de.bluecolored.bluemap.core.world.Chunk;
import de.bluecolored.bluemap.core.world.EmptyChunk;
import de.bluecolored.bluemap.core.world.Region;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
public class MCARegion implements Region {
public static final String FILE_SUFFIX = ".mca";
private final MCAWorld world;
private final Path regionFile;
private final Vector2i regionPos;
public MCARegion(MCAWorld world, Path regionFile) throws IllegalArgumentException {
this.world = world;
this.regionFile = regionFile;
String[] filenameParts = regionFile.getFileName().toString().split("\\.");
int rX = Integer.parseInt(filenameParts[1]);
int rZ = Integer.parseInt(filenameParts[2]);
this.regionPos = new Vector2i(rX, rZ);
}
@Override
public Chunk loadChunk(int chunkX, int chunkZ, boolean ignoreMissingLightData) throws IOException {
if (Files.notExists(regionFile)) return EmptyChunk.INSTANCE;
long fileLength = Files.size(regionFile);
if (fileLength == 0) return EmptyChunk.INSTANCE;
try (RandomAccessFile raf = new RandomAccessFile(regionFile.toFile(), "r")) {
int xzChunk = Math.floorMod(chunkZ, 32) * 32 + Math.floorMod(chunkX, 32);
raf.seek(xzChunk * 4L);
int offset = raf.read() << 16;
offset |= (raf.read() & 0xFF) << 8;
offset |= raf.read() & 0xFF;
offset *= 4096;
int size = raf.readByte() * 4096;
if (size == 0) {
return EmptyChunk.INSTANCE;
}
raf.seek(offset + 4); // +4 skip chunk size
byte compressionByte = raf.readByte();
Compression compression;
switch (compressionByte) {
case 0:
case 3: compression = Compression.NONE; break;
case 1: compression = Compression.GZIP; break;
case 2: compression = Compression.DEFLATE; break;
default: throw new IOException("Invalid compression type " + compressionByte);
}
DataInputStream dis = new DataInputStream(new BufferedInputStream(compression.decompress(new FileInputStream(raf.getFD()))));
ChunkData chunkData = MCAMath.BLUENBT.read(dis, ChunkData.class);
return MCAChunk.create(world, chunkData);
} catch (RuntimeException e) {
Logger.global.logError("Failed to load Chunk!", e);
throw new IOException(e);
}
}
@Override
public Collection<Vector2i> listChunks(long modifiedSince) {
if (Files.notExists(regionFile)) return Collections.emptyList();
try {
long fileLength = Files.size(regionFile);
if (fileLength == 0) return Collections.emptyList();
} catch (IOException ex) {
Logger.global.logWarning("Failed to read file-size for file: " + regionFile);
return Collections.emptyList();
}
List<Vector2i> chunks = new ArrayList<>(1024); //1024 = 32 x 32 chunks per region-file
try (RandomAccessFile raf = new RandomAccessFile(regionFile.toFile(), "r")) {
for (int x = 0; x < 32; x++) {
for (int z = 0; z < 32; z++) {
Vector2i chunk = new Vector2i(regionPos.getX() * 32 + x, regionPos.getY() * 32 + z);
int xzChunk = z * 32 + x;
raf.seek(xzChunk * 4 + 3);
int size = raf.readByte() * 4096;
if (size == 0) continue;
raf.seek(xzChunk * 4 + 4096);
int timestamp = raf.read() << 24;
timestamp |= (raf.read() & 0xFF) << 16;
timestamp |= (raf.read() & 0xFF) << 8;
timestamp |= raf.read() & 0xFF;
if (timestamp >= (modifiedSince / 1000)) {
chunks.add(chunk);
}
}
}
} catch (RuntimeException | IOException ex) {
Logger.global.logWarning("Failed to read .mca file: " + regionFile + " (" + ex + ")");
}
return chunks;
}
@Override
public Path getRegionFile() {
return regionFile;
}
public static String getRegionFileName(int regionX, int regionZ) {
return "r." + regionX + "." + regionZ + FILE_SUFFIX;
}
}

View File

@ -1,16 +0,0 @@
package de.bluecolored.bluemap.core.mca.resourcepack;
import de.bluecolored.bluemap.api.debug.DebugDump;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
@NoArgsConstructor
@AllArgsConstructor
@Getter
@DebugDump
public class Dimension {
private String type = "minecraft:overworld";
}

View File

@ -1,65 +0,0 @@
package de.bluecolored.bluemap.core.mca.resourcepack;
import de.bluecolored.bluemap.api.debug.DebugDump;
import lombok.*;
@NoArgsConstructor
@AllArgsConstructor
@Getter
@DebugDump
public class DimensionType {
private static final DimensionType OVERWORLD = new DimensionType();
private static final DimensionType NETHER = new DimensionType(
true,
false,
8.0,
false,
true,
0.1f,
128,
0,
256,
"#minecraft:infiniburn_nether",
"minecraft:the_nether"
);
private static final DimensionType END = new DimensionType(
false,
false,
1.0,
false,
false,
0,
256,
0,
256,
"#minecraft:infiniburn_end",
"minecraft:the_end"
);
private static final DimensionType OVERWORLD_CAVES = new DimensionType(
false,
true,
1.0,
true,
true,
0,
256,
-64,
384,
"#minecraft:infiniburn_overworld",
"minecraft:overworld"
);
private boolean ultrawarm = false;
private boolean natural = true;
private double coordinateScale = 1.0;
private boolean hasSkylight = true;
private boolean hasCeiling = false;
private float ambientLight = 0;
private int logicalHeight = 256;
private int minY = -64;
private int height = 384;
private String infiniburn = "#minecraft:infiniburn_overworld";
private String effects = "minecraft:overworld";
}

View File

@ -29,7 +29,7 @@ import com.google.gson.stream.JsonReader;
import de.bluecolored.bluemap.api.debug.DebugDump;
import de.bluecolored.bluemap.core.util.math.Color;
import de.bluecolored.bluemap.core.world.Biome;
import de.bluecolored.bluemap.core.world.BlockNeighborhood;
import de.bluecolored.bluemap.core.world.block.BlockNeighborhood;
import java.awt.image.BufferedImage;
import java.io.BufferedReader;

View File

@ -77,9 +77,6 @@ public class ResourcePath<T> extends Key {
if (filePath.getNameCount() < 4)
throw new IllegalArgumentException("The provided filePath has less than 4 segments!");
if (!filePath.getName(0).toString().equalsIgnoreCase("assets"))
throw new IllegalArgumentException("The provided filePath doesn't start with 'assets'!");
String namespace = filePath.getName(1).toString();
String path = filePath.subpath(3, filePath.getNameCount()).toString().replace(filePath.getFileSystem().getSeparator(), "/");

View File

@ -25,22 +25,21 @@
package de.bluecolored.bluemap.core.resources.adapter;
import com.flowpowered.math.vector.*;
import com.google.gson.FieldNamingPolicy;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import de.bluecolored.bluemap.core.resources.resourcepack.blockmodel.Face;
import de.bluecolored.bluemap.core.util.Direction;
import de.bluecolored.bluemap.core.util.math.Axis;
import de.bluecolored.bluemap.core.util.math.Color;
import java.io.IOException;
import java.util.EnumMap;
public class ResourcesGson {
public static final Gson INSTANCE = addAdapter(new GsonBuilder())
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
.setLenient()
.create();
@ -60,9 +59,4 @@ public class ResourcesGson {
);
}
public static String nextStringOrBoolean(JsonReader in) throws IOException {
if (in.peek() == JsonToken.BOOLEAN) return Boolean.toString(in.nextBoolean());
return in.nextString();
}
}

View File

@ -25,8 +25,10 @@
package de.bluecolored.bluemap.core.resources.biome.datapack;
import de.bluecolored.bluemap.core.world.Biome;
import lombok.Getter;
@SuppressWarnings("FieldMayBeFinal")
@Getter
public class DpBiome {
private DpBiomeEffects effects = new DpBiomeEffects();
@ -44,16 +46,4 @@ public class DpBiome {
);
}
public DpBiomeEffects getEffects() {
return effects;
}
public double getTemperature() {
return temperature;
}
public double getDownfall() {
return downfall;
}
}

View File

@ -26,24 +26,14 @@ package de.bluecolored.bluemap.core.resources.biome.datapack;
import de.bluecolored.bluemap.core.util.math.Color;
import de.bluecolored.bluemap.core.world.Biome;
import lombok.Getter;
@SuppressWarnings("FieldMayBeFinal")
@Getter
public class DpBiomeEffects {
private Color water_color = Biome.DEFAULT.getWaterColor();
private Color foliage_color = Biome.DEFAULT.getOverlayFoliageColor();
private Color grass_color = Biome.DEFAULT.getOverlayGrassColor();
public Color getWaterColor() {
return water_color;
}
public Color getFoliageColor() {
return foliage_color;
}
public Color getGrassColor() {
return grass_color;
}
private Color waterColor = Biome.DEFAULT.getWaterColor();
private Color foliageColor = Biome.DEFAULT.getOverlayFoliageColor();
private Color grassColor = Biome.DEFAULT.getOverlayGrassColor();
}

View File

@ -0,0 +1,111 @@
package de.bluecolored.bluemap.core.resources.datapack;
import de.bluecolored.bluemap.core.logger.Logger;
import de.bluecolored.bluemap.core.resources.ResourcePath;
import de.bluecolored.bluemap.core.resources.adapter.ResourcesGson;
import de.bluecolored.bluemap.core.resources.datapack.dimension.DimensionTypeData;
import de.bluecolored.bluemap.core.util.Key;
import de.bluecolored.bluemap.core.world.DimensionType;
import org.jetbrains.annotations.Nullable;
import java.io.BufferedReader;
import java.io.IOException;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CompletionException;
import java.util.stream.Stream;
public class DataPack {
public static final Key DIMENSION_OVERWORLD = new Key("minecraft", "overworld");
public static final Key DIMENSION_THE_NETHER = new Key("minecraft", "the_nether");
public static final Key DIMENSION_THE_END = new Key("minecraft", "the_end");
private final Map<Key, DimensionType> dimensionTypes = new HashMap<>();
@Nullable
public DimensionType getDimensionType(Key key) {
return dimensionTypes.get(key);
}
public void load(Path root) {
Logger.global.logDebug("Loading datapack from: " + root + " ...");
loadPath(root);
}
private void loadPath(Path root) {
if (!Files.isDirectory(root)) {
try (FileSystem fileSystem = FileSystems.newFileSystem(root, (ClassLoader) null)) {
for (Path fsRoot : fileSystem.getRootDirectories()) {
if (!Files.isDirectory(fsRoot)) continue;
loadPath(fsRoot);
}
} catch (Exception ex) {
Logger.global.logDebug("Failed to read '" + root + "': " + ex);
}
return;
}
list(root.resolve("data"))
.map(path -> path.resolve("dimension_type"))
.filter(Files::isDirectory)
.flatMap(DataPack::walk)
.filter(path -> path.getFileName().toString().endsWith(".json"))
.filter(Files::isRegularFile)
.forEach(file -> loadResource(root, file, () -> {
try (BufferedReader reader = Files.newBufferedReader(file)) {
return ResourcesGson.INSTANCE.fromJson(reader, DimensionTypeData.class);
}
}, dimensionTypes));
}
private <T> void loadResource(Path root, Path file, Loader<T> loader, Map<Key, T> resultMap) {
try {
ResourcePath<T> resourcePath = new ResourcePath<>(root.relativize(file));
if (resultMap.containsKey(resourcePath)) return; // don't load already present resources
T resource = loader.load();
if (resource == null) return; // don't load missing resources
resourcePath.setResource(resource);
resultMap.put(resourcePath, resource);
} catch (Exception ex) {
Logger.global.logDebug("Failed to parse resource-file '" + file + "': " + ex);
}
}
public void bake() {
dimensionTypes.putIfAbsent(new Key("minecraft", "overworld"), DimensionType.OVERWORLD);
dimensionTypes.putIfAbsent(new Key("minecraft", "overworld_caves"), DimensionType.OVERWORLD_CAVES);
dimensionTypes.putIfAbsent(new Key("minecraft", "the_nether"), DimensionType.NETHER);
dimensionTypes.putIfAbsent(new Key("minecraft", "the_end"), DimensionType.END);
}
private static Stream<Path> list(Path root) {
if (!Files.isDirectory(root)) return Stream.empty();
try {
return Files.list(root);
} catch (IOException ex) {
throw new CompletionException(ex);
}
}
private static Stream<Path> walk(Path root) {
if (!Files.exists(root)) return Stream.empty();
if (Files.isRegularFile(root)) return Stream.of(root);
try {
return Files.walk(root);
} catch (IOException ex) {
throw new CompletionException(ex);
}
}
private interface Loader<T> {
T load() throws IOException;
}
}

View File

@ -0,0 +1,21 @@
package de.bluecolored.bluemap.core.resources.datapack.dimension;
import de.bluecolored.bluemap.api.debug.DebugDump;
import de.bluecolored.bluemap.core.world.DimensionType;
import lombok.*;
import lombok.experimental.Accessors;
@Data
@DebugDump
public class DimensionTypeData implements DimensionType {
private boolean natural;
@Accessors(fluent = true) private boolean hasSkylight;
@Accessors(fluent = true) private boolean hasCeiling;
private float ambientLight;
private int minY;
private int height;
private Long fixedTime;
private double coordinateScale;
}

View File

@ -299,6 +299,7 @@ public class ResourcePack {
}, BlueMap.THREAD_POOL),
// load biome configs
// TODO: move this to datapacks?
CompletableFuture.runAsync(() -> {
list(root.resolve("assets"))
.map(path -> path.resolve("biomes.json"))

View File

@ -27,9 +27,9 @@ package de.bluecolored.bluemap.core.resources.resourcepack.blockstate;
import com.google.gson.Gson;
import com.google.gson.annotations.JsonAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import de.bluecolored.bluemap.api.debug.DebugDump;
import de.bluecolored.bluemap.core.resources.AbstractTypeAdapterFactory;
import de.bluecolored.bluemap.core.resources.adapter.ResourcesGson;
import de.bluecolored.bluemap.core.world.BlockState;
import org.apache.commons.lang3.StringUtils;
@ -125,7 +125,7 @@ public class Multipart {
andConditions.add(
BlockStateCondition.and(andArray.toArray(new BlockStateCondition[0])));
} else {
String[] values = StringUtils.split(ResourcesGson.nextStringOrBoolean(in), '|');
String[] values = StringUtils.split(nextStringOrBoolean(in), '|');
andConditions.add(BlockStateCondition.property(name, values));
}
}
@ -134,6 +134,11 @@ public class Multipart {
return BlockStateCondition.and(andConditions.toArray(new BlockStateCondition[0]));
}
private String nextStringOrBoolean(JsonReader in) throws IOException {
if (in.peek() == JsonToken.BOOLEAN) return Boolean.toString(in.nextBoolean());
return in.nextString();
}
}
}

View File

@ -24,16 +24,16 @@
*/
package de.bluecolored.bluemap.core.storage;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
public class CompressedInputStream extends InputStream {
public class CompressedInputStream extends FilterInputStream {
private final InputStream in;
private final Compression compression;
public CompressedInputStream(InputStream in, Compression compression) {
this.in = in;
super(in);
this.compression = compression;
}
@ -45,29 +45,4 @@ public class CompressedInputStream extends InputStream {
return compression;
}
@Override
public int read() throws IOException {
return in.read();
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
return in.read(b, off, len);
}
@Override
public void close() throws IOException {
in.close();
}
@Override
public int available() throws IOException {
return in.available();
}
@Override
public void reset() throws IOException {
in.reset();
}
}

View File

@ -29,7 +29,7 @@ import de.bluecolored.bluemap.core.storage.CompressedInputStream;
import de.bluecolored.bluemap.core.storage.Compression;
import de.bluecolored.bluemap.core.storage.sql.dialect.Dialect;
import de.bluecolored.bluemap.core.storage.sql.dialect.PostgresDialect;
import de.bluecolored.bluemap.core.util.WrappedOutputStream;
import de.bluecolored.bluemap.core.util.OnCloseOutputStream;
import java.io.*;
import java.net.MalformedURLException;
@ -51,7 +51,7 @@ public class PostgreSQLStorage extends SQLStorage {
public OutputStream writeMapTile(String mapId, int lod, Vector2i tile) throws IOException {
Compression compression = lod == 0 ? this.hiresCompression : Compression.NONE;
ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
return new WrappedOutputStream(compression.compress(byteOut), () -> {
return new OnCloseOutputStream(compression.compress(byteOut), () -> {
int mapFK = getMapFK(mapId);
int tileCompressionFK = getMapTileCompressionFK(compression);
@ -71,7 +71,7 @@ public class PostgreSQLStorage extends SQLStorage {
@Override
public OutputStream writeMeta(String mapId, String name) {
ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
return new WrappedOutputStream(byteOut, () -> {
return new OnCloseOutputStream(byteOut, () -> {
int mapFK = getMapFK(mapId);
recoveringConnection(connection -> {
executeUpdate(connection, this.dialect.writeMeta(),

View File

@ -32,7 +32,7 @@ import de.bluecolored.bluemap.core.logger.Logger;
import de.bluecolored.bluemap.core.storage.*;
import de.bluecolored.bluemap.core.storage.sql.dialect.DialectType;
import de.bluecolored.bluemap.core.storage.sql.dialect.Dialect;
import de.bluecolored.bluemap.core.util.WrappedOutputStream;
import de.bluecolored.bluemap.core.util.OnCloseOutputStream;
import org.apache.commons.dbcp2.*;
import org.apache.commons.pool2.ObjectPool;
import org.apache.commons.pool2.impl.GenericObjectPool;
@ -108,7 +108,7 @@ public abstract class SQLStorage extends Storage {
Compression compression = lod == 0 ? this.hiresCompression : Compression.NONE;
ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
return new WrappedOutputStream(compression.compress(byteOut), () -> {
return new OnCloseOutputStream(compression.compress(byteOut), () -> {
int mapFK = getMapFK(mapId);
int tileCompressionFK = getMapTileCompressionFK(compression);
@ -234,7 +234,7 @@ public abstract class SQLStorage extends Storage {
@Override
public OutputStream writeMeta(String mapId, String name) {
ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
return new WrappedOutputStream(byteOut, () -> {
return new OnCloseOutputStream(byteOut, () -> {
int mapFK = getMapFK(mapId);
recoveringConnection(connection -> {

View File

@ -40,7 +40,7 @@ public class FileHelper {
final Path partFile = getPartFile(file);
FileHelper.createDirectories(partFile.getParent());
OutputStream os = Files.newOutputStream(partFile, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE);
return new WrappedOutputStream(os, () -> {
return new OnCloseOutputStream(os, () -> {
if (!Files.exists(partFile)) return;
FileHelper.createDirectories(file.getParent());
FileHelper.move(partFile, file);

View File

@ -22,7 +22,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package de.bluecolored.bluemap.core.world;
package de.bluecolored.bluemap.core.util;
import com.flowpowered.math.vector.Vector2i;

View File

@ -73,7 +73,7 @@ public class Key {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Key that = (Key) o;
return getFormatted() == that.getFormatted();
return formatted == that.formatted;
}
@Override

View File

@ -24,34 +24,19 @@
*/
package de.bluecolored.bluemap.core.util;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
public class WrappedInputStream extends InputStream {
public class OnCloseInputStream extends FilterInputStream {
private final InputStream in;
private final AutoCloseable onClose;
public WrappedInputStream(InputStream in, AutoCloseable onClose) {
this.in = in;
public OnCloseInputStream(InputStream in, AutoCloseable onClose) {
super(in);
this.onClose = onClose;
}
@Override
public int read() throws IOException {
return in.read();
}
@Override
public int read(byte[] b) throws IOException {
return in.read(b);
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
return in.read(b, off, len);
}
@Override
public void close() throws IOException {
IOException ioExcetion = null;

View File

@ -24,39 +24,19 @@
*/
package de.bluecolored.bluemap.core.util;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.OutputStream;
public class WrappedOutputStream extends OutputStream {
public class OnCloseOutputStream extends FilterOutputStream {
private final OutputStream out;
private final AutoCloseable onClose;
public WrappedOutputStream(OutputStream out, AutoCloseable onClose) {
this.out = out;
public OnCloseOutputStream(OutputStream out, AutoCloseable onClose) {
super(out);
this.onClose = onClose;
}
@Override
public void write(int b) throws IOException {
out.write(b);
}
@Override
public void write(byte[] b) throws IOException {
out.write(b);
}
@Override
public void write(byte[] b, int off, int len) throws IOException {
out.write(b, off, len);
}
@Override
public void flush() throws IOException {
out.flush();
}
@Override
public void close() throws IOException {
IOException ioExcetion = null;

View File

@ -26,22 +26,50 @@ package de.bluecolored.bluemap.core.world;
public interface Chunk {
boolean isGenerated();
Chunk EMPTY_CHUNK = new Chunk() {};
long getInhabitedTime();
default boolean isGenerated() {
return false;
}
BlockState getBlockState(int x, int y, int z);
default boolean hasLightData() {
return false;
}
LightData getLightData(int x, int y, int z, LightData target);
default long getInhabitedTime() {
return 0;
}
String getBiome(int x, int y, int z);
default BlockState getBlockState(int x, int y, int z) {
return BlockState.AIR;
}
int getMaxY(int x, int z);
default LightData getLightData(int x, int y, int z, LightData target) {
return target.set(0, 0);
}
int getMinY(int x, int z);
default String getBiome(int x, int y, int z) {
return Biome.DEFAULT.getFormatted();
}
int getWorldSurfaceY(int x, int z);
default int getMaxY(int x, int z) {
return 255;
}
int getOceanFloorY(int x, int z);
default int getMinY(int x, int z) {
return 0;
}
default boolean hasWorldSurfaceHeights() {
return false;
}
default int getWorldSurfaceY(int x, int z) { return 0; }
default boolean hasOceanFloorHeights() {
return false;
}
default int getOceanFloorY(int x, int z) { return 0; }
}

View File

@ -0,0 +1,30 @@
package de.bluecolored.bluemap.core.world;
@FunctionalInterface
public interface ChunkConsumer {
default boolean filter(int chunkX, int chunkZ, long lastModified) {
return true;
}
void accept(int chunkX, int chunkZ, Chunk chunk);
@FunctionalInterface
interface ListOnly extends ChunkConsumer {
void accept(int chunkX, int chunkZ, long lastModified);
@Override
default boolean filter(int chunkX, int chunkZ, long lastModified) {
accept(chunkX, chunkZ, lastModified);
return false;
}
@Override
default void accept(int chunkX, int chunkZ, Chunk chunk) {
throw new IllegalStateException("Should never be called.");
}
}
}

View File

@ -0,0 +1,82 @@
package de.bluecolored.bluemap.core.world;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.experimental.Accessors;
public interface DimensionType {
DimensionType OVERWORLD = new Builtin(
true,
true,
false,
0f,
-64,
384,
null,
1.0
);
DimensionType OVERWORLD_CAVES = new Builtin(
true,
true,
true,
0,
-64,
384,
null,
1.0
);
DimensionType NETHER = new Builtin(
false,
false,
true,
0.1f,
0,
256,
6000L,
8.0
);
DimensionType END = new Builtin(
false,
false,
false,
0,
0,
256,
18000L,
1.0
);
boolean isNatural();
boolean hasSkylight();
boolean hasCeiling();
float getAmbientLight();
int getMinY();
int getHeight();
Long getFixedTime();
double getCoordinateScale();
@Getter
@AllArgsConstructor(access = AccessLevel.PRIVATE)
class Builtin implements DimensionType {
private final boolean natural;
@Accessors(fluent = true) private final boolean hasSkylight;
@Accessors(fluent = true) private final boolean hasCeiling;
private final float ambientLight;
private final int minY;
private final int height;
private final Long fixedTime;
private final double coordinateScale;
}
}

View File

@ -24,34 +24,41 @@
*/
package de.bluecolored.bluemap.core.world;
import com.flowpowered.math.vector.Vector2i;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Collection;
public interface Region {
/**
* Returns a collection of all generated chunks.<br>
* <i>(Be aware that the collection is not cached and recollected each time from the world-files!)</i>
* Directly loads and returns the specified chunk.<br>
* (implementations should consider overriding this method for a faster implementation)
*/
default Collection<Vector2i> listChunks(){
return listChunks(0);
default Chunk loadChunk(int chunkX, int chunkZ) throws IOException {
class SingleChunkConsumer implements ChunkConsumer {
private Chunk foundChunk = Chunk.EMPTY_CHUNK;
@Override
public boolean filter(int x, int z, long lastModified) {
return x == chunkX && z == chunkZ;
}
@Override
public void accept(int chunkX, int chunkZ, Chunk chunk) {
this.foundChunk = chunk;
}
}
SingleChunkConsumer singleChunkConsumer = new SingleChunkConsumer();
iterateAllChunks(singleChunkConsumer);
return singleChunkConsumer.foundChunk;
}
/**
* Returns a collection of all chunks that have been modified at or after the specified timestamp.<br>
* <i>(Be aware that the collection is not cached and recollected each time from the world-files!)</i>
* Iterates over all chunks in this region and first calls {@link ChunkConsumer#filter(int, int, long)}.<br>
* And if (any only if) that method returned <code>true</code>, the chunk will be loaded and {@link ChunkConsumer#accept(int, int, Chunk)}
* will be called with the loaded chunk.
* @param consumer the consumer choosing which chunks to load and accepting them
* @throws IOException if an IOException occurred trying to read the region
*/
Collection<Vector2i> listChunks(long modifiedSince);
default Chunk loadChunk(int chunkX, int chunkZ) throws IOException {
return loadChunk(chunkX, chunkZ, false);
}
Chunk loadChunk(int chunkX, int chunkZ, boolean ignoreMissingLightData) throws IOException;
Path getRegionFile();
void iterateAllChunks(ChunkConsumer consumer) throws IOException;
}

View File

@ -26,10 +26,11 @@ package de.bluecolored.bluemap.core.world;
import com.flowpowered.math.vector.Vector2i;
import com.flowpowered.math.vector.Vector3i;
import de.bluecolored.bluemap.core.util.Grid;
import de.bluecolored.bluemap.core.util.Key;
import java.nio.file.Path;
import java.util.Collection;
import java.util.UUID;
/**
* Represents a World on the Server<br>
@ -38,17 +39,15 @@ import java.util.UUID;
*/
public interface World {
Path getSaveFolder();
Path getWorldFolder();
Key getDimension();
String getName();
int getSkyLight();
Vector3i getSpawnPoint();
int getMaxY(int x, int z);
int getMinY(int x, int z);
DimensionType getDimensionType();
Grid getChunkGrid();
@ -57,7 +56,7 @@ public interface World {
/**
* Returns the {@link Chunk} on the specified block-position
*/
Chunk getChunkAtBlock(int x, int y, int z);
Chunk getChunkAtBlock(int x, int z);
/**
* Returns the {@link Chunk} on the specified chunk-position
@ -75,6 +74,11 @@ public interface World {
*/
Collection<Vector2i> listRegions();
/**
* Loads all chunks from the specified region into the chunk cache (if there is a cache)
*/
void preloadRegionChunks(int x, int z);
/**
* Invalidates the complete chunk cache (if there is a cache), so that every chunk has to be reloaded from disk
*/

View File

@ -22,7 +22,12 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package de.bluecolored.bluemap.core.world;
package de.bluecolored.bluemap.core.world.block;
import de.bluecolored.bluemap.core.world.BlockState;
import de.bluecolored.bluemap.core.world.Chunk;
import de.bluecolored.bluemap.core.world.LightData;
import de.bluecolored.bluemap.core.world.World;
public class Block<T extends Block<T>> {
@ -131,7 +136,7 @@ public class Block<T extends Block<T>> {
}
public Chunk getChunk() {
if (chunk == null) chunk = world.getChunkAtBlock(x, y, z);
if (chunk == null) chunk = world.getChunkAtBlock(x, z);
return chunk;
}

View File

@ -22,10 +22,11 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package de.bluecolored.bluemap.core.world;
package de.bluecolored.bluemap.core.world.block;
import de.bluecolored.bluemap.core.map.hires.RenderSettings;
import de.bluecolored.bluemap.core.resources.resourcepack.ResourcePack;
import de.bluecolored.bluemap.core.world.World;
public class BlockNeighborhood<T extends BlockNeighborhood<T>> extends ExtendedBlock<T> {

View File

@ -22,10 +22,11 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package de.bluecolored.bluemap.core.world;
package de.bluecolored.bluemap.core.world.block;
import de.bluecolored.bluemap.core.map.hires.RenderSettings;
import de.bluecolored.bluemap.core.resources.resourcepack.ResourcePack;
import de.bluecolored.bluemap.core.world.*;
import java.util.Objects;
@ -36,6 +37,7 @@ public class ExtendedBlock<T extends ExtendedBlock<T>> extends Block<T> {
private Biome biome;
private boolean insideRenderBoundsCalculated, insideRenderBounds;
private boolean isCaveCalculated, isCave;
public ExtendedBlock(ResourcePack resourcePack, RenderSettings renderSettings, World world, int x, int y, int z) {
super(world, x, y, z);
@ -51,6 +53,7 @@ public class ExtendedBlock<T extends ExtendedBlock<T>> extends Block<T> {
this.biome = null;
this.insideRenderBoundsCalculated = false;
this.isCaveCalculated = false;
}
@Override
@ -62,7 +65,7 @@ public class ExtendedBlock<T extends ExtendedBlock<T>> extends Block<T> {
@Override
public LightData getLightData() {
LightData ld = super.getLightData();
if (renderSettings.isRenderEdges() && !isInsideRenderBounds()) ld.set(getWorld().getSkyLight(), ld.getBlockLight());
if (renderSettings.isRenderEdges() && !isInsideRenderBounds()) ld.set(getWorld().getDimensionType().hasSkylight() ? 16 : 0, ld.getBlockLight());
return ld;
}
@ -90,6 +93,18 @@ public class ExtendedBlock<T extends ExtendedBlock<T>> extends Block<T> {
return insideRenderBounds;
}
public boolean isCave() {
if (!isCaveCalculated) {
isCave = getY() < renderSettings.getRemoveCavesBelowY() &&
!getChunk().hasOceanFloorHeights() ||
getY() < getChunk().getOceanFloorY(getX(), getZ()) +
renderSettings.getCaveDetectionOceanFloor();
isCaveCalculated = true;
}
return isCave;
}
public ResourcePack getResourcePack() {
return resourcePack;
}

View File

@ -22,18 +22,21 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package de.bluecolored.bluemap.core.mca;
package de.bluecolored.bluemap.core.world.mca;
import com.google.gson.reflect.TypeToken;
import de.bluecolored.bluemap.core.mca.deserializer.BlockStateDeserializer;
import de.bluecolored.bluemap.core.util.Key;
import de.bluecolored.bluemap.core.world.BlockState;
import de.bluecolored.bluemap.core.world.mca.data.BlockStateDeserializer;
import de.bluecolored.bluemap.core.world.mca.data.KeyDeserializer;
import de.bluecolored.bluenbt.BlueNBT;
public class MCAMath {
public class MCAUtil {
public static final BlueNBT BLUENBT = new BlueNBT();
static {
BLUENBT.register(TypeToken.get(BlockState.class), new BlockStateDeserializer());
BLUENBT.register(TypeToken.get(Key.class), new KeyDeserializer());
}
/**
@ -53,17 +56,18 @@ public class MCAMath {
/**
* Treating the long array "data" as a continuous stream of bits, returning the "valueIndex"-th value when each value has "bitsPerValue" bits.
*/
@SuppressWarnings("ShiftOutOfRange")
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)
int bitOffset = bitIndex & 0x3F; // Math.floorMod(index, 64)
if (firstLong >= data.length) return 0;
long value = data[firstLong] >>> bitoffset;
long value = data[firstLong] >>> bitOffset;
if (bitoffset > 0 && firstLong + 1 < data.length) {
if (bitOffset > 0 && firstLong + 1 < data.length) {
long value2 = data[firstLong + 1];
value2 = value2 << -bitoffset;
value2 = value2 << -bitOffset;
value = value | value2;
}

View File

@ -0,0 +1,253 @@
package de.bluecolored.bluemap.core.world.mca;
import com.flowpowered.math.vector.Vector2i;
import com.flowpowered.math.vector.Vector3i;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import de.bluecolored.bluemap.core.BlueMap;
import de.bluecolored.bluemap.core.logger.Logger;
import de.bluecolored.bluemap.core.resources.datapack.DataPack;
import de.bluecolored.bluemap.core.util.Grid;
import de.bluecolored.bluemap.core.util.Key;
import de.bluecolored.bluemap.core.util.Vector2iCache;
import de.bluecolored.bluemap.core.world.*;
import de.bluecolored.bluemap.core.world.mca.chunk.ChunkLoader;
import de.bluecolored.bluemap.core.world.mca.data.LevelData;
import de.bluecolored.bluemap.core.world.mca.region.RegionType;
import lombok.Getter;
import lombok.ToString;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.zip.GZIPInputStream;
@Getter
@ToString
public class MCAWorld implements World {
private static final Grid CHUNK_GRID = new Grid(16);
private static final Grid REGION_GRID = new Grid(32).multiply(CHUNK_GRID);
private static final Vector2iCache VECTOR_2_I_CACHE = new Vector2iCache();
private final Path worldFolder;
private final Key dimension;
private final LevelData levelData;
private final DataPack dataPack;
private final DimensionType dimensionType;
private final Vector3i spawnPoint;
private final Path dimensionFolder;
private final Path regionFolder;
private final ChunkLoader chunkLoader = new ChunkLoader();
private final LoadingCache<Vector2i, Region> regionCache = Caffeine.newBuilder()
.executor(BlueMap.THREAD_POOL)
.maximumSize(64)
.expireAfterWrite(10, TimeUnit.MINUTES)
.build(this::loadRegion);
private final LoadingCache<Vector2i, Chunk> chunkCache = Caffeine.newBuilder()
.executor(BlueMap.THREAD_POOL)
.maximumSize(2048) // 2 regions worth of chunks
.expireAfterWrite(10, TimeUnit.MINUTES)
.build(this::loadChunk);
public MCAWorld(Path worldFolder, Key dimension, LevelData levelData, DataPack dataPack) {
this.worldFolder = worldFolder;
this.dimension = dimension;
this.levelData = levelData;
this.dataPack = dataPack;
LevelData.Dimension dim = levelData.getData().getWorldGenSettings().getDimensions().get(dimension.getFormatted());
if (dim == null) {
Logger.global.logWarning("The level-data does not contain any dimension with the id '" + dimension +
"', using fallback.");
dim = new LevelData.Dimension();
}
DimensionType dimensionType = dataPack.getDimensionType(new Key(dim.getType()));
if (dimensionType == null) {
Logger.global.logWarning("The data-pack for world '" + worldFolder +
"' does not contain any dimension-type with the id '" + dim.getType() + "', using fallback.");
dimensionType = DimensionType.OVERWORLD;
}
this.dimensionType = dimensionType;
this.spawnPoint = new Vector3i(
levelData.getData().getSpawnX(),
levelData.getData().getSpawnY(),
levelData.getData().getSpawnZ()
);
this.dimensionFolder = resolveDimensionFolder(worldFolder, dimension);
this.regionFolder = getWorldFolder().resolve("region");
}
@Override
public String getName() {
return levelData.getData().getLevelName();
}
@Override
public Grid getChunkGrid() {
return CHUNK_GRID;
}
@Override
public Grid getRegionGrid() {
return REGION_GRID;
}
@Override
public Chunk getChunkAtBlock(int x, int z) {
return getChunk(x >> 4, z >> 4);
}
@Override
public Chunk getChunk(int x, int z) {
return getChunk(VECTOR_2_I_CACHE.get(x, z));
}
private Chunk getChunk(Vector2i pos) {
return chunkCache.get(pos);
}
@Override
public Region getRegion(int x, int z) {
return getRegion(VECTOR_2_I_CACHE.get(x, z));
}
private Region getRegion(Vector2i pos) {
return regionCache.get(pos);
}
@Override
public Collection<Vector2i> listRegions() {
File[] regionFiles = getRegionFolder().toFile().listFiles();
if (regionFiles == null) return Collections.emptyList();
List<Vector2i> regions = new ArrayList<>(regionFiles.length);
for (File file : regionFiles) {
if (RegionType.forFileName(file.getName()) == null) continue;
if (file.length() <= 0) continue;
try {
String[] filenameParts = file.getName().split("\\.");
int rX = Integer.parseInt(filenameParts[1]);
int rZ = Integer.parseInt(filenameParts[2]);
regions.add(new Vector2i(rX, rZ));
} catch (NumberFormatException ignore) {}
}
return regions;
}
@Override
public void preloadRegionChunks(int x, int z) {
try {
getRegion(x, z).iterateAllChunks((cx, cz, chunk) -> {
Vector2i chunkPos = VECTOR_2_I_CACHE.get(cx, cz);
chunkCache.put(chunkPos, chunk);
});
} catch (IOException ex) {
Logger.global.logDebug("Unexpected exception trying to load preload region (x:" + x + ", z:" + z + "):" + ex);
}
}
@Override
public void invalidateChunkCache() {
chunkCache.invalidateAll();
}
@Override
public void invalidateChunkCache(int x, int z) {
chunkCache.invalidate(VECTOR_2_I_CACHE.get(x, z));
}
@Override
public void cleanUpChunkCache() {
chunkCache.cleanUp();
}
private Region loadRegion(Vector2i regionPos) {
return loadRegion(regionPos.getX(), regionPos.getY());
}
private Region loadRegion(int x, int z) {
return RegionType.loadRegion(this, getRegionFolder(), x, z);
}
private Chunk loadChunk(Vector2i chunkPos) {
return loadChunk(chunkPos.getX(), chunkPos.getY());
}
private Chunk loadChunk(int x, int z) {
final int tries = 3;
final int tryInterval = 1000;
Exception loadException = null;
for (int i = 0; i < tries; i++) {
try {
return getRegion(x >> 5, z >> 5)
.loadChunk(x, z);
} catch (IOException | RuntimeException e) {
if (loadException != null) e.addSuppressed(loadException);
loadException = e;
if (i + 1 < tries) {
try {
Thread.sleep(tryInterval);
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
break;
}
}
}
}
Logger.global.logDebug("Unexpected exception trying to load chunk (x:" + x + ", z:" + z + "):" + loadException);
return Chunk.EMPTY_CHUNK;
}
public static MCAWorld load(Path worldFolder, Key dimension) throws IOException {
// load level.dat
Path levelFile = worldFolder.resolve("level.dat");
InputStream levelFileIn = new GZIPInputStream(new BufferedInputStream(Files.newInputStream(levelFile)));
LevelData levelData = MCAUtil.BLUENBT.read(levelFileIn, LevelData.class);
// load datapacks
DataPack dataPack = new DataPack();
Path dataPackFolder = worldFolder.resolve("datapacks");
if (Files.exists(dataPackFolder)) {
List<Path> roots;
try (var stream = Files.list(dataPackFolder)) {
roots = stream
.sorted(Comparator.reverseOrder())
.collect(Collectors.toList());
}
for (Path root : roots) {
dataPack.load(root);
}
}
dataPack.bake();
// create world
return new MCAWorld(worldFolder, dimension, levelData, dataPack);
}
private static Path resolveDimensionFolder(Path worldFolder, Key dimension) {
if (DataPack.DIMENSION_OVERWORLD.equals(dimension)) return worldFolder;
if (DataPack.DIMENSION_THE_NETHER.equals(dimension)) return worldFolder.resolve("DIM-1");
if (DataPack.DIMENSION_THE_END.equals(dimension)) return worldFolder.resolve("DIM1");
return worldFolder.resolve("dimensions").resolve(dimension.getNamespace()).resolve(dimension.getValue());
}
}

View File

@ -1,7 +1,9 @@
package de.bluecolored.bluemap.core.mca;
package de.bluecolored.bluemap.core.world.mca;
public class PackedIntArrayAccess {
private static final int[] INDEX_PARAMETERS = new int[]{
// magic constants for fast division
private static final int[] DIVISION_MAGIC = new int[]{
-1, -1, 0,
Integer.MIN_VALUE, 0, 0,
1431655765, 1431655765, 0,
@ -71,33 +73,46 @@ public class PackedIntArrayAccess {
private final int bitsPerElement;
private final long[] data;
private final long maxValue;
private final int elementsPerLong, indexShift;
private final long indexScale, indexOffset;
private final long maxValue, indexScale, indexOffset;
public PackedIntArrayAccess(long[] data, int elementCount) {
this(Math.max(data.length * Long.SIZE / elementCount, 1), data);
}
public PackedIntArrayAccess(int bitsPerElement, long[] data) {
this.bitsPerElement = bitsPerElement;
this.data = data;
this.maxValue = (1L << this.bitsPerElement) - 1L;
this.elementsPerLong = (char)(64 / this.bitsPerElement);
this.elementsPerLong = 64 / this.bitsPerElement;
int i = 3 * (this.elementsPerLong - 1);
this.indexScale = Integer.toUnsignedLong(INDEX_PARAMETERS[i]);
this.indexOffset = Integer.toUnsignedLong(INDEX_PARAMETERS[i + 1]);
this.indexShift = INDEX_PARAMETERS[i + 2];
this.indexScale = Integer.toUnsignedLong(DIVISION_MAGIC[i]);
this.indexOffset = Integer.toUnsignedLong(DIVISION_MAGIC[i + 1]);
this.indexShift = DIVISION_MAGIC[i + 2] + 32;
}
public int get(int i) {
int j = this.storageIndex(i);
if (j >= this.data.length) return 0;
long l = this.data[j];
int k = (i - j * this.elementsPerLong) * this.bitsPerElement;
return (int)(l >> k & this.maxValue);
int storageIndex = this.storageIndex(i);
if (storageIndex >= this.data.length) return 0;
long l = this.data[storageIndex];
int offset = (i - storageIndex * this.elementsPerLong) * this.bitsPerElement;
return (int)(l >> offset & this.maxValue);
}
public int storageIndex(int i) {
return (int) ((long) i * this.indexScale + this.indexOffset >> 32 >> this.indexShift);
private int storageIndex(int i) {
// this is the same as doing: floor(i / elementsPerLong)
return (int) ((long) i * this.indexScale + this.indexOffset >> this.indexShift);
}
}
public int getCapacity() {
return data.length * elementsPerLong;
}
public boolean isCorrectSize(int expectedSize) {
int capacity = getCapacity();
return expectedSize <= capacity && expectedSize + elementsPerLong > capacity;
}
}

View File

@ -0,0 +1,70 @@
package de.bluecolored.bluemap.core.world.mca.chunk;
import de.bluecolored.bluemap.core.storage.Compression;
import de.bluecolored.bluemap.core.world.mca.MCAUtil;
import de.bluecolored.bluemap.core.world.mca.region.MCARegion;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.jetbrains.annotations.Nullable;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.function.BiFunction;
public class ChunkLoader {
// sorted list of chunk-versions, loaders at the start of the list are preferred over loaders at the end
private static final List<ChunkVersionLoader<?>> CHUNK_VERSION_LOADERS = List.of(
new ChunkVersionLoader<>(Chunk_1_18.Data.class, Chunk_1_18::new, 0)
);
private ChunkVersionLoader<?> lastUsedLoader = CHUNK_VERSION_LOADERS.get(0);
public MCAChunk load(MCARegion region, byte[] data, int offset, int length, Compression compression) throws IOException {
InputStream in = new ByteArrayInputStream(data, offset, length);
in.mark(-1);
// try last used version
ChunkVersionLoader<?> usedLoader = lastUsedLoader;
MCAChunk chunk = usedLoader.load(region, compression.decompress(in));
// check version and reload chunk if the wrong loader has been used and a better one has been found
ChunkVersionLoader<?> actualLoader = findBestLoaderForVersion(chunk.getDataVersion());
if (actualLoader != null && usedLoader != actualLoader) {
in.reset(); // reset read position
chunk = actualLoader.load(region, compression.decompress(in));
lastUsedLoader = actualLoader;
}
return chunk;
}
private @Nullable ChunkVersionLoader<?> findBestLoaderForVersion(int version) {
for (ChunkVersionLoader<?> loader : CHUNK_VERSION_LOADERS) {
if (loader.mightSupport(version)) return loader;
}
return null;
}
@RequiredArgsConstructor
@Getter
private static class ChunkVersionLoader<D extends MCAChunk.Data> {
private final Class<D> dataType;
private final BiFunction<MCARegion, D, MCAChunk> constructor;
private final int dataVersion;
public MCAChunk load(MCARegion region, InputStream in) throws IOException {
D data = MCAUtil.BLUENBT.read(in, dataType);
return mightSupport(data.getDataVersion()) ? constructor.apply(region, data) : new MCAChunk(region, data) {};
}
public boolean mightSupport(int dataVersion) {
return dataVersion >= this.dataVersion;
}
}
}

View File

@ -0,0 +1,278 @@
package de.bluecolored.bluemap.core.world.mca.chunk;
import de.bluecolored.bluemap.core.logger.Logger;
import de.bluecolored.bluemap.core.util.Key;
import de.bluecolored.bluemap.core.world.Biome;
import de.bluecolored.bluemap.core.world.BlockState;
import de.bluecolored.bluemap.core.world.DimensionType;
import de.bluecolored.bluemap.core.world.LightData;
import de.bluecolored.bluemap.core.world.mca.MCAUtil;
import de.bluecolored.bluemap.core.world.mca.PackedIntArrayAccess;
import de.bluecolored.bluemap.core.world.mca.region.MCARegion;
import de.bluecolored.bluenbt.NBTName;
import lombok.Getter;
import org.jetbrains.annotations.Nullable;
public class Chunk_1_18 extends MCAChunk {
private static final BlockStatesData EMPTY_BLOCKSTATESDATA = new BlockStatesData();
private static final BiomesData EMPTY_BIOMESDATA = new BiomesData();
private static final HeightmapsData EMPTY_HEIGHTMAPS_DATA = new HeightmapsData();
private static final Key STATUS_EMPTY = new Key("minecraft", "empty");
private static final Key STATUS_FULL = new Key("minecraft", "full");
private final boolean generated;
private final boolean hasLightData;
private final long inhabitedTime;
private final int skyLight;
private final int worldMinY;
private final boolean hasWorldSurfaceHeights;
private final PackedIntArrayAccess worldSurfaceHeights;
private final boolean hasOceanFloorHeights;
private final PackedIntArrayAccess oceanFloorHeights;
private final Section[] sections;
private final int sectionMin, sectionMax;
public Chunk_1_18(MCARegion region, Data data) {
super(region, data);
this.generated = !STATUS_EMPTY.equals(data.status);
this.hasLightData = STATUS_FULL.equals(data.status);
this.inhabitedTime = data.inhabitedTime;
DimensionType dimensionType = getRegion().getWorld().getDimensionType();
this.worldMinY = dimensionType.getMinY();
this.skyLight = dimensionType.hasSkylight() ? 16 : 0;
int worldHeight = dimensionType.getHeight();
int bitsPerHeightmapElement = MCAUtil.ceilLog2(worldHeight + 1);
this.worldSurfaceHeights = new PackedIntArrayAccess(bitsPerHeightmapElement, data.getHeightmaps().getWorldSurface());
this.oceanFloorHeights = new PackedIntArrayAccess(bitsPerHeightmapElement, data.getHeightmaps().getOceanFloor());
this.hasWorldSurfaceHeights = this.worldSurfaceHeights.isCorrectSize(VALUES_PER_HEIGHTMAP);
this.hasOceanFloorHeights = this.oceanFloorHeights.isCorrectSize(VALUES_PER_HEIGHTMAP);
SectionData[] sectionsData = data.getSections();
if (sectionsData != null && sectionsData.length > 0) {
int min = Integer.MAX_VALUE;
int max = Integer.MIN_VALUE;
// find section min/max y
for (SectionData sectionData : sectionsData) {
int y = sectionData.getY();
if (min > y) min = y;
if (max < y) max = y;
}
// load sections into ordered array
this.sections = new Section[1 + max - min];
for (SectionData sectionData : sectionsData) {
Section section = new Section(sectionData);
int y = section.getSectionY();
if (min > y) min = y;
if (max < y) max = y;
this.sections[section.sectionY - min] = section;
}
this.sectionMin = min;
this.sectionMax = max;
} else {
this.sections = new Section[0];
this.sectionMin = 0;
this.sectionMax = 0;
}
}
@Override
public boolean isGenerated() {
return generated;
}
@Override
public boolean hasLightData() {
return hasLightData;
}
@Override
public long getInhabitedTime() {
return inhabitedTime;
}
@Override
public BlockState getBlockState(int x, int y, int z) {
Section section = getSection(y >> 4);
if (section == null) return BlockState.AIR;
return section.getBlockState(x, y, z);
}
@Override
public String getBiome(int x, int y, int z) {
Section section = getSection(y >> 4);
if (section == null) return Biome.DEFAULT.getFormatted();
return section.getBiome(x, y, z);
}
@Override
public LightData getLightData(int x, int y, int z, LightData target) {
if (!hasLightData) return target.set(skyLight, 0);
int sectionY = y >> 4;
Section section = getSection(sectionY);
if (section == null) return (sectionY < sectionMin) ? target.set(0, 0) : target.set(skyLight, 0);
return section.getLightData(x, y, z, target);
}
@Override
public int getMinY(int x, int z) {
return sectionMin * 16;
}
@Override
public int getMaxY(int x, int z) {
return sectionMax * 16 + 15;
}
@Override
public boolean hasWorldSurfaceHeights() {
return hasWorldSurfaceHeights;
}
@Override
public int getWorldSurfaceY(int x, int z) {
return worldSurfaceHeights.get((z & 0xF) << 4 | x & 0xF) + worldMinY;
}
@Override
public boolean hasOceanFloorHeights() {
return hasOceanFloorHeights;
}
@Override
public int getOceanFloorY(int x, int z) {
return oceanFloorHeights.get((z & 0xF) << 4 | x & 0xF) + worldMinY;
}
private Section getSection(int y) {
y -= sectionMin;
if (y < 0 || y >= this.sections.length) return null;
return this.sections[y];
}
protected static class Section {
private final int sectionY;
private final BlockState[] blockPalette;
private final String[] biomePalette;
private final PackedIntArrayAccess blocks;
private final PackedIntArrayAccess biomes;
private final byte[] blockLight;
private final byte[] skyLight;
public Section(SectionData sectionData) {
this.sectionY = sectionData.getY();
this.blockPalette = sectionData.getBlockStates().getPalette();
this.biomePalette = sectionData.getBiomes().getPalette();
this.blocks = new PackedIntArrayAccess(sectionData.getBlockStates().getData(), BLOCKS_PER_SECTION);
this.biomes = new PackedIntArrayAccess(sectionData.getBiomes().getData(), BIOMES_PER_SECTION);
this.blockLight = sectionData.getBlockLight();
this.skyLight = sectionData.getSkyLight();
}
public BlockState getBlockState(int x, int y, int z) {
if (blockPalette.length == 1) return blockPalette[0];
if (blockPalette.length == 0) return BlockState.AIR;
int id = blocks.get((y & 0xF) << 8 | (z & 0xF) << 4 | x & 0xF);
if (id >= blockPalette.length) {
Logger.global.noFloodWarning("palette-warning", "Got block-palette id " + id + " but palette has size of " + blockPalette.length + "! (Future occasions of this error will not be logged)");
return BlockState.MISSING;
}
return blockPalette[id];
}
public String getBiome(int x, int y, int z) {
if (biomePalette.length == 1) return biomePalette[0];
if (biomePalette.length == 0) return Biome.DEFAULT.getValue();
int id = biomes.get((y & 0b1100) << 2 | z & 0b1100 | (x & 0b1100) >> 2);
if (id >= biomePalette.length) {
Logger.global.noFloodWarning("biome-palette-warning", "Got biome-palette id " + id + " but palette has size of " + biomePalette.length + "! (Future occasions of this error will not be logged)");
return Biome.DEFAULT.getValue();
}
return biomePalette[id];
}
public LightData getLightData(int x, int y, int z, LightData target) {
if (blockLight.length == 0 && skyLight.length == 0) return target.set(0, 0);
int blockByteIndex = (y & 0xF) << 8 | (z & 0xF) << 4 | x & 0xF;
int blockHalfByteIndex = blockByteIndex >> 1; // blockByteIndex / 2
boolean largeHalf = (blockByteIndex & 0x1) != 0; // (blockByteIndex % 2) == 0
return target.set(
this.skyLight.length > blockHalfByteIndex ? MCAUtil.getByteHalf(this.skyLight[blockHalfByteIndex], largeHalf) : 0,
this.blockLight.length > blockHalfByteIndex ? MCAUtil.getByteHalf(this.blockLight[blockHalfByteIndex], largeHalf) : 0
);
}
public int getSectionY() {
return sectionY;
}
}
@Getter
@SuppressWarnings("FieldMayBeFinal")
public static class Data extends MCAChunk.Data {
private Key status = STATUS_EMPTY;
private long inhabitedTime = 0;
private HeightmapsData heightmaps = EMPTY_HEIGHTMAPS_DATA;
private SectionData @Nullable [] sections = null;
}
@Getter
@SuppressWarnings("FieldMayBeFinal")
protected static class HeightmapsData {
@NBTName("WORLD_SURFACE") private long[] worldSurface = EMPTY_LONG_ARRAY;
@NBTName("OCEAN_FLOOR") private long[] oceanFloor = EMPTY_LONG_ARRAY;
}
@Getter
@SuppressWarnings("FieldMayBeFinal")
protected static class SectionData {
private int y = 0;
private byte[] blockLight = EMPTY_BYTE_ARRAY;
private byte[] skyLight = EMPTY_BYTE_ARRAY;
@NBTName("block_states") private BlockStatesData blockStates = EMPTY_BLOCKSTATESDATA;
private BiomesData biomes = EMPTY_BIOMESDATA;
}
@Getter
@SuppressWarnings("FieldMayBeFinal")
protected static class BlockStatesData {
private BlockState[] palette = EMPTY_BLOCKSTATE_ARRAY;
private long[] data = EMPTY_LONG_ARRAY;
}
@Getter
@SuppressWarnings("FieldMayBeFinal")
protected static class BiomesData {
private String[] palette = EMPTY_STRING_ARRAY;
private long[] data = EMPTY_LONG_ARRAY;
}
}

View File

@ -0,0 +1,36 @@
package de.bluecolored.bluemap.core.world.mca.chunk;
import de.bluecolored.bluemap.core.world.BlockState;
import de.bluecolored.bluemap.core.world.Chunk;
import de.bluecolored.bluemap.core.world.mca.region.MCARegion;
import lombok.Getter;
import lombok.ToString;
@Getter
@ToString
public abstract class MCAChunk implements Chunk {
protected static final int BLOCKS_PER_SECTION = 16 * 16 * 16;
protected static final int BIOMES_PER_SECTION = 4 * 4 * 4;
protected static final int VALUES_PER_HEIGHTMAP = 16 * 16;
protected static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
protected static final long[] EMPTY_LONG_ARRAY = new long[0];
protected static final String[] EMPTY_STRING_ARRAY = new String[0];
protected static final BlockState[] EMPTY_BLOCKSTATE_ARRAY = new BlockState[0];
private final MCARegion region;
private final int dataVersion;
public MCAChunk(MCARegion region, Data chunkData) {
this.region = region;
this.dataVersion = chunkData.getDataVersion();
}
@SuppressWarnings("FieldMayBeFinal")
@Getter
public static class Data {
private int dataVersion = 0;
}
}

View File

@ -1,4 +1,4 @@
package de.bluecolored.bluemap.core.mca.deserializer;
package de.bluecolored.bluemap.core.world.mca.data;
import de.bluecolored.bluemap.core.world.BlockState;
import de.bluecolored.bluenbt.NBTReader;
@ -18,27 +18,23 @@ public class BlockStateDeserializer implements TypeDeserializer<BlockState> {
Map<String, String> properties = null;
while (reader.hasNext()) {
String name = reader.name();
if (name.equals("Name")){
id = reader.nextString();
} else if (name.equals("Properties")) {
properties = new LinkedHashMap<>();
reader.beginCompound();
while (reader.hasNext())
properties.put(reader.name(), reader.nextString());
reader.endCompound();
} else {
reader.skip();
switch (reader.name()) {
case "Name" : id = reader.nextString(); break;
case "Properties" :
properties = new LinkedHashMap<>();
reader.beginCompound();
while (reader.hasNext())
properties.put(reader.name(), reader.nextString());
reader.endCompound();
break;
default : reader.skip();
}
}
reader.endCompound();
if (id == null) throw new IOException("Invalid BlockState, Name is missing!");
if (properties == null)
return new BlockState(id);
return new BlockState(id, properties);
return properties == null ? new BlockState(id) : new BlockState(id, properties);
}
}
}

View File

@ -0,0 +1,16 @@
package de.bluecolored.bluemap.core.world.mca.data;
import de.bluecolored.bluemap.core.util.Key;
import de.bluecolored.bluenbt.NBTReader;
import de.bluecolored.bluenbt.TypeDeserializer;
import java.io.IOException;
public class KeyDeserializer implements TypeDeserializer<Key> {
@Override
public Key read(NBTReader reader) throws IOException {
return new Key(reader.nextString());
}
}

View File

@ -0,0 +1,31 @@
package de.bluecolored.bluemap.core.world.mca.data;
import lombok.Getter;
import java.util.HashMap;
import java.util.Map;
@Getter
@SuppressWarnings("FieldMayBeFinal")
public class LevelData {
private Data data = new Data();
@Getter
public static class Data {
private String levelName = "world";
private int spawnX = 0, spawnY = 0, spawnZ = 0;
private WGSettings worldGenSettings = new WGSettings();
}
@Getter
public static class WGSettings {
private Map<String, Dimension> dimensions = new HashMap<>();
}
@Getter
public static class Dimension {
private String type = "minecraft:overworld";
}
}

View File

@ -0,0 +1,188 @@
/*
* 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.world.mca.region;
import com.flowpowered.math.vector.Vector2i;
import de.bluecolored.bluemap.core.storage.Compression;
import de.bluecolored.bluemap.core.world.Chunk;
import de.bluecolored.bluemap.core.world.ChunkConsumer;
import de.bluecolored.bluemap.core.world.Region;
import de.bluecolored.bluemap.core.world.mca.MCAWorld;
import de.bluecolored.bluemap.core.world.mca.chunk.MCAChunk;
import lombok.Getter;
import lombok.ToString;
import java.io.EOFException;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.ReadableByteChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
@Getter
@ToString
public class MCARegion implements Region {
public static final String FILE_SUFFIX = ".mca";
private final MCAWorld world;
private final Path regionFile;
private final Vector2i regionPos;
public MCARegion(MCAWorld world, Path regionFile) throws IllegalArgumentException {
this.world = world;
this.regionFile = regionFile;
String[] filenameParts = regionFile.getFileName().toString().split("\\.");
int rX = Integer.parseInt(filenameParts[1]);
int rZ = Integer.parseInt(filenameParts[2]);
this.regionPos = new Vector2i(rX, rZ);
}
public MCARegion(MCAWorld world, Vector2i regionPos) throws IllegalArgumentException {
this.world = world;
this.regionPos = regionPos;
this.regionFile = world.getRegionFolder().resolve(getRegionFileName(regionPos.getX(), regionPos.getY()));
}
@Override
public Chunk loadChunk(int chunkX, int chunkZ) throws IOException {
if (Files.notExists(regionFile)) return Chunk.EMPTY_CHUNK;
long fileLength = Files.size(regionFile);
if (fileLength == 0) return Chunk.EMPTY_CHUNK;
try (FileChannel channel = FileChannel.open(regionFile, StandardOpenOption.READ)) {
int xzChunk = (chunkZ & 0b11111) << 5 | (chunkX & 0b11111);
byte[] header = new byte[4];
channel.position(xzChunk * 4);
readFully(channel, header, 0, 4);
int offset = header[0] << 16;
offset |= (header[1] & 0xFF) << 8;
offset |= header[2] & 0xFF;
offset *= 4096;
int size = header[3] * 4096;
if (size == 0) return Chunk.EMPTY_CHUNK;
return loadChunk(channel, offset, size, new byte[size]);
}
}
@Override
public void iterateAllChunks(ChunkConsumer consumer) throws IOException {
if (Files.notExists(regionFile)) return;
long fileLength = Files.size(regionFile);
if (fileLength == 0) return;
int chunkStartX = regionPos.getX() * 32;
int chunkStartZ = regionPos.getY() * 32;
try (FileChannel channel = FileChannel.open(regionFile, StandardOpenOption.READ)) {
byte[] header = new byte[1024 * 8];
byte[] chunkDataBuffer = null;
// read the header
readFully(channel, header, 0, header.length);
// iterate over all chunks
for (int x = 0; x < 32; x++) {
for (int z = 0; z < 32; z++) {
int xzChunk = z * 32 + x;
int size = header[xzChunk * 4 + 3] * 4096;
if (size == 0) continue;
int chunkX = chunkStartX + x;
int chunkZ = chunkStartZ + z;
int i = xzChunk * 4 + 4096;
int timestamp = header[i++] << 24;
timestamp |= (header[i++] & 0xFF) << 16;
timestamp |= (header[i++] & 0xFF) << 8;
timestamp |= header[i] & 0xFF;
// load chunk only if consumers filter returns true
if (consumer.filter(chunkX, chunkZ, timestamp)) {
i = xzChunk * 4;
int offset = header[i++] << 16;
offset |= (header[i++] & 0xFF) << 8;
offset |= header[i] & 0xFF;
offset *= 4096;
if (chunkDataBuffer == null || chunkDataBuffer.length < size)
chunkDataBuffer = new byte[size];
MCAChunk chunk = loadChunk(channel, offset, size, chunkDataBuffer);
consumer.accept(chunkX, chunkZ, chunk);
}
}
}
}
}
private MCAChunk loadChunk(FileChannel channel, int offset, int size, byte[] dataBuffer) throws IOException {
channel.position(offset);
readFully(channel, dataBuffer, 0, size);
int compressionTypeId = dataBuffer[4];
Compression compression;
switch (compressionTypeId) {
case 0 :
case 3 : compression = Compression.NONE; break;
case 1 : compression = Compression.GZIP; break;
case 2 : compression = Compression.DEFLATE; break;
default: throw new IOException("Unknown chunk compression-id: " + compressionTypeId);
}
return world.getChunkLoader().load(this, dataBuffer, 5, size - 5, compression);
}
public static String getRegionFileName(int regionX, int regionZ) {
return "r." + regionX + "." + regionZ + FILE_SUFFIX;
}
@SuppressWarnings("SameParameterValue")
private static void readFully(ReadableByteChannel src, byte[] dst, int off, int len) throws IOException {
readFully(src, ByteBuffer.wrap(dst), off, len);
}
private static void readFully(ReadableByteChannel src, ByteBuffer bb, int off, int len) throws IOException {
int n = 0;
while (n < len) {
bb.limit(Math.min(off + len, bb.capacity()));
bb.position(off);
int count = src.read(bb);
if (count < 0) throw new EOFException();
n += count;
}
}
}

View File

@ -22,10 +22,10 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package de.bluecolored.bluemap.core.mca.region;
package de.bluecolored.bluemap.core.world.mca.region;
import de.bluecolored.bluemap.core.mca.MCAWorld;
import de.bluecolored.bluemap.core.world.Region;
import de.bluecolored.bluemap.core.world.mca.MCAWorld;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@ -34,8 +34,8 @@ import java.nio.file.Path;
public enum RegionType {
MCA (MCARegion::new, MCARegion.FILE_SUFFIX, MCARegion::getRegionFileName),
LINEAR (LinearRegion::new, LinearRegion.FILE_SUFFIX, LinearRegion::getRegionFileName);
MCA (MCARegion::new, MCARegion.FILE_SUFFIX, MCARegion::getRegionFileName);
//LINEAR (LinearRegion::new, LinearRegion.FILE_SUFFIX, LinearRegion::getRegionFileName);
// we do this to improve performance, as calling values() creates a new array each time
private final static RegionType[] VALUES = values();

View File

@ -25,6 +25,7 @@
package de.bluecolored.bluemap.core.world;
import com.flowpowered.math.vector.Vector2i;
import de.bluecolored.bluemap.core.util.Grid;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;

View File

@ -106,7 +106,7 @@ tasks.shadowJar {
//relocate ("com.flowpowered.math", "de.bluecolored.shadow.flowpowered.math") //DON"T relocate this, because the API depends on it
relocate ("com.typesafe.config", "de.bluecolored.shadow.typesafe.config")
relocate ("net.querz.nbt", "de.bluecolored.shadow.querz.nbt")
relocate ("de.bluecolored.bluenbt", "de.bluecolored.shadow.bluecolored.bluenbt")
relocate ("org.spongepowered.configurate", "de.bluecolored.shadow.configurate")
relocate ("com.github.benmanes.caffeine", "de.bluecolored.shadow.benmanes.caffeine")
relocate ("org.aopalliance", "de.bluecolored.shadow.aopalliance")