From 32311faccbd188663d770c32ccf37b06bccd309b Mon Sep 17 00:00:00 2001 From: "Lukas Rieger (Blue)" Date: Wed, 8 Nov 2023 13:27:51 +0100 Subject: [PATCH] Core rewrite done --- .../bluemap/common/BlueMapService.java | 5 +- .../bluemap/common/WebFilesManager.java | 14 +- .../typeserializer/KeyTypeSerializer.java | 52 +--- .../common/plugin/commands/Commands.java | 10 +- .../common/rendermanager/MapUpdateTask.java | 2 +- .../rendermanager/WorldRegionRenderTask.java | 29 +- BlueMapCore/build.gradle.kts | 9 +- .../bluecolored/bluemap/core/map/BmMap.java | 14 +- .../bluemap/core/map/TextureGallery.java | 11 +- .../core/map/hires/HiresModelManager.java | 2 +- .../core/map/hires/HiresModelRenderer.java | 8 +- .../core/map/hires/RenderSettings.java | 4 + .../blockmodel/BlockStateModelFactory.java | 2 +- .../hires/blockmodel/LiquidModelBuilder.java | 11 +- .../blockmodel/ResourceModelBuilder.java | 11 +- .../bluemap/core/map/lowres/LowresLayer.java | 2 +- .../core/map/lowres/LowresTileManager.java | 2 +- .../bluemap/core/mca/ChunkAnvil113.java | 252 ---------------- .../bluemap/core/mca/ChunkAnvil115.java | 257 ---------------- .../bluemap/core/mca/ChunkAnvil116.java | 239 --------------- .../bluemap/core/mca/ChunkAnvil118.java | 247 --------------- .../bluemap/core/mca/LegacyBiomes.java | 116 ------- .../bluemap/core/mca/MCAChunk.java | 114 ------- .../bluemap/core/mca/MCAWorld.java | 285 ------------------ .../bluemap/core/mca/data/BiomesData.java | 14 - .../core/mca/data/BlockStatesData.java | 15 - .../bluemap/core/mca/data/ChunkData.java | 20 -- .../bluemap/core/mca/data/HeightmapsData.java | 17 -- .../bluemap/core/mca/data/LevelData.java | 19 -- .../bluemap/core/mca/data/SectionData.java | 28 -- .../bluemap/core/mca/region/LinearRegion.java | 209 ------------- .../bluemap/core/mca/region/MCARegion.java | 162 ---------- .../core/mca/resourcepack/Dimension.java | 16 - .../core/mca/resourcepack/DimensionType.java | 65 ---- .../BlockColorCalculatorFactory.java | 2 +- .../bluemap/core/resources/ResourcePath.java | 3 - .../core/resources/adapter/ResourcesGson.java | 10 +- .../resources/biome/datapack/DpBiome.java | 14 +- .../biome/datapack/DpBiomeEffects.java | 20 +- .../core/resources/datapack/DataPack.java | 111 +++++++ .../datapack/dimension/DimensionTypeData.java | 21 ++ .../resources/resourcepack/ResourcePack.java | 1 + .../resourcepack/blockstate/Multipart.java | 9 +- .../core/storage/CompressedInputStream.java | 31 +- .../core/storage/sql/PostgreSQLStorage.java | 6 +- .../bluemap/core/storage/sql/SQLStorage.java | 6 +- .../bluemap/core/util/FileHelper.java | 2 +- .../bluemap/core/{world => util}/Grid.java | 2 +- .../de/bluecolored/bluemap/core/util/Key.java | 2 +- ...putStream.java => OnCloseInputStream.java} | 23 +- ...utStream.java => OnCloseOutputStream.java} | 28 +- .../bluecolored/bluemap/core/world/Chunk.java | 46 ++- .../bluemap/core/world/ChunkConsumer.java | 30 ++ .../bluemap/core/world/DimensionType.java | 82 +++++ .../bluemap/core/world/Region.java | 45 +-- .../bluecolored/bluemap/core/world/World.java | 20 +- .../bluemap/core/world/{ => block}/Block.java | 9 +- .../world/{ => block}/BlockNeighborhood.java | 3 +- .../core/world/{ => block}/ExtendedBlock.java | 19 +- .../MCAMath.java => world/mca/MCAUtil.java} | 18 +- .../bluemap/core/world/mca/MCAWorld.java | 253 ++++++++++++++++ .../{ => world}/mca/PackedIntArrayAccess.java | 47 ++- .../core/world/mca/chunk/ChunkLoader.java | 70 +++++ .../core/world/mca/chunk/Chunk_1_18.java | 278 +++++++++++++++++ .../core/world/mca/chunk/MCAChunk.java | 36 +++ .../mca/data}/BlockStateDeserializer.java | 30 +- .../core/world/mca/data/KeyDeserializer.java | 16 + .../core/world/mca/data/LevelData.java | 31 ++ .../core/world/mca/region/MCARegion.java | 188 ++++++++++++ .../{ => world}/mca/region/RegionType.java | 8 +- .../bluemap/core/world/GridTest.java | 1 + implementations/fabric-1.20/build.gradle.kts | 2 +- 72 files changed, 1411 insertions(+), 2375 deletions(-) rename BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/EmptyChunk.java => BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/config/typeserializer/KeyTypeSerializer.java (58%) delete mode 100644 BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/ChunkAnvil113.java delete mode 100644 BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/ChunkAnvil115.java delete mode 100644 BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/ChunkAnvil116.java delete mode 100644 BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/ChunkAnvil118.java delete mode 100644 BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/LegacyBiomes.java delete mode 100644 BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/MCAChunk.java delete mode 100644 BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/MCAWorld.java delete mode 100644 BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/data/BiomesData.java delete mode 100644 BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/data/BlockStatesData.java delete mode 100644 BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/data/ChunkData.java delete mode 100644 BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/data/HeightmapsData.java delete mode 100644 BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/data/LevelData.java delete mode 100644 BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/data/SectionData.java delete mode 100644 BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/region/LinearRegion.java delete mode 100644 BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/region/MCARegion.java delete mode 100644 BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/resourcepack/Dimension.java delete mode 100644 BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/resourcepack/DimensionType.java create mode 100644 BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/datapack/DataPack.java create mode 100644 BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/datapack/dimension/DimensionTypeData.java rename BlueMapCore/src/main/java/de/bluecolored/bluemap/core/{world => util}/Grid.java (99%) rename BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/{WrappedInputStream.java => OnCloseInputStream.java} (81%) rename BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/{WrappedOutputStream.java => OnCloseOutputStream.java} (78%) create mode 100644 BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/ChunkConsumer.java create mode 100644 BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/DimensionType.java rename BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/{ => block}/Block.java (94%) rename BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/{ => block}/BlockNeighborhood.java (97%) rename BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/{ => block}/ExtendedBlock.java (82%) rename BlueMapCore/src/main/java/de/bluecolored/bluemap/core/{mca/MCAMath.java => world/mca/MCAUtil.java} (84%) create mode 100644 BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/MCAWorld.java rename BlueMapCore/src/main/java/de/bluecolored/bluemap/core/{ => world}/mca/PackedIntArrayAccess.java (68%) create mode 100644 BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/chunk/ChunkLoader.java create mode 100644 BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/chunk/Chunk_1_18.java create mode 100644 BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/chunk/MCAChunk.java rename BlueMapCore/src/main/java/de/bluecolored/bluemap/core/{mca/deserializer => world/mca/data}/BlockStateDeserializer.java (51%) create mode 100644 BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/data/KeyDeserializer.java create mode 100644 BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/data/LevelData.java create mode 100644 BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/region/MCARegion.java rename BlueMapCore/src/main/java/de/bluecolored/bluemap/core/{ => world}/mca/region/RegionType.java (94%) diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/BlueMapService.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/BlueMapService.java index 3e1746c0..fdf7496e 100644 --- a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/BlueMapService.java +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/BlueMapService.java @@ -42,11 +42,12 @@ 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 @@ private synchronized void loadMapConfig(String id, MapConfig mapConfig) throws C 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( diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/WebFilesManager.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/WebFilesManager.java index 466fd177..3e33c2b8 100644 --- a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/WebFilesManager.java +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/WebFilesManager.java @@ -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 @@ 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 Path getSettingsFile() { 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 void saveSettings() throws IOException { 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); } } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/EmptyChunk.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/config/typeserializer/KeyTypeSerializer.java similarity index 58% rename from BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/EmptyChunk.java rename to BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/config/typeserializer/KeyTypeSerializer.java index 089dedd8..a153fc2a 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/EmptyChunk.java +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/config/typeserializer/KeyTypeSerializer.java @@ -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 { @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; } - } diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/commands/Commands.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/commands/Commands.java index 827c6438..e2b7f70c 100644 --- a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/commands/Commands.java +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/commands/Commands.java @@ -53,7 +53,7 @@ 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 int updateCommand(CommandContext context, boolean force) { try { List 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 int worldsCommand(CommandContext context) { 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; diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/rendermanager/MapUpdateTask.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/rendermanager/MapUpdateTask.java index 70362ed9..d4f19739 100644 --- a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/rendermanager/MapUpdateTask.java +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/rendermanager/MapUpdateTask.java @@ -27,7 +27,7 @@ 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; diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/rendermanager/WorldRegionRenderTask.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/rendermanager/WorldRegionRenderTask.java index f84fb993..aa1ace4a 100644 --- a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/rendermanager/WorldRegionRenderTask.java +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/rendermanager/WorldRegionRenderTask.java @@ -27,11 +27,14 @@ 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 @@ private synchronized void init() { Set 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 chunks = region.listChunks(changesSince); + Collection 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 @@ private synchronized void init() { .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 void doWork() { this.atWork++; } - //Logger.global.logInfo("Working on " + worldRegion + " - Tile " + tile); if (tileRenderPreconditions(tile)) { map.renderTile(tile); // <- actual work } @@ -163,6 +173,7 @@ private boolean tileRenderPreconditions(Vector2i tile) { 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 @@ private boolean tileRenderPreconditions(Vector2i tile) { private void complete() { map.getRenderState().setRenderTime(worldRegion, startTime); - - //Logger.global.logInfo("Done with: " + worldRegion); } @Override diff --git a/BlueMapCore/build.gradle.kts b/BlueMapCore/build.gradle.kts index b19e01b6..4c74eaeb 100644 --- a/BlueMapCore/build.gradle.kts +++ b/BlueMapCore/build.gradle.kts @@ -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") diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/BmMap.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/BmMap.java index 2ef3a923..c8d7c7f4 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/BmMap.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/BmMap.java @@ -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.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 @@ private void saveMapSettings() { 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); } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/TextureGallery.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/TextureGallery.java index f1e4426e..ca060ce2 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/TextureGallery.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/TextureGallery.java @@ -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 @@ @DebugDump public class TextureGallery { + private static final Gson GSON = ResourcesGson.addAdapter(new GsonBuilder()) + .setFieldNamingPolicy(FieldNamingPolicy.IDENTITY) + .create(); + private final Map, Integer> ordinalMap; private int nextId; @@ -83,7 +90,7 @@ public void writeTexturesFile(ResourcePack resourcePack, OutputStream out) throw }); 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 void writeTexturesFile(ResourcePack resourcePack, OutputStream out) throw 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++) { diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/HiresModelManager.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/HiresModelManager.java index 669cc71d..822a0d4f 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/HiresModelManager.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/HiresModelManager.java @@ -31,7 +31,7 @@ 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; diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/HiresModelRenderer.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/HiresModelRenderer.java index d7fc65e7..f9677ea4 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/HiresModelRenderer.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/HiresModelRenderer.java @@ -30,7 +30,8 @@ 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 void render(World world, Vector3i modelMin, Vector3i modelMax, HiresTileM 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); diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/RenderSettings.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/RenderSettings.java index bb3b03b2..8c3a4d8e 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/RenderSettings.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/RenderSettings.java @@ -78,6 +78,10 @@ default boolean isRenderEdges() { return true; } + default boolean isIgnoreMissingLightData() { + return false; + } + default boolean isInsideRenderBoundaries(int x, int z) { Vector3i min = getMinPos(); Vector3i max = getMaxPos(); diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/blockmodel/BlockStateModelFactory.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/blockmodel/BlockStateModelFactory.java index b094aefb..3539043c 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/blockmodel/BlockStateModelFactory.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/blockmodel/BlockStateModelFactory.java @@ -31,7 +31,7 @@ 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; diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/blockmodel/LiquidModelBuilder.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/blockmodel/LiquidModelBuilder.java index e8717a65..0c05a6ca 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/blockmodel/LiquidModelBuilder.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/blockmodel/LiquidModelBuilder.java @@ -42,9 +42,9 @@ 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 void build(BlockNeighborhood block, BlockState blockState, Variant var 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)))){ diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/blockmodel/ResourceModelBuilder.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/blockmodel/ResourceModelBuilder.java index d2eb1746..b015c458 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/blockmodel/ResourceModelBuilder.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/blockmodel/ResourceModelBuilder.java @@ -45,9 +45,9 @@ 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 void build(BlockNeighborhood block, Variant variant, BlockModelView bl 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 @@ private void createElementFace(Element element, Direction faceDir, VectorM3f c0, 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(); diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/lowres/LowresLayer.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/lowres/LowresLayer.java index f6296716..9b6b1034 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/lowres/LowresLayer.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/lowres/LowresLayer.java @@ -31,7 +31,7 @@ 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; diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/lowres/LowresTileManager.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/lowres/LowresTileManager.java index dbb01784..05be3dc6 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/lowres/LowresTileManager.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/lowres/LowresTileManager.java @@ -27,7 +27,7 @@ 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 { diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/ChunkAnvil113.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/ChunkAnvil113.java deleted file mode 100644 index b0541a48..00000000 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/ChunkAnvil113.java +++ /dev/null @@ -1,252 +0,0 @@ -/* - * This file is part of BlueMap, licensed under the MIT License (MIT). - * - * Copyright (c) Blue (Lukas Rieger) - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package de.bluecolored.bluemap.core.mca; - -@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) 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 paletteTag = (ListTag) sectionData.getListTag("Palette"); - if (paletteTag != null) { - this.palette = new BlockState[paletteTag.size()]; - for (int i = 0; i < this.palette.length; i++) { - CompoundTag stateTag = paletteTag.get(i); - - String id = stateTag.getString("Name"); //shortcut to save time and memory - if (id.equals(AIR_ID)) { - palette[i] = BlockState.AIR; - continue; - } - - Map properties = new HashMap<>(); - - if (stateTag.containsKey("Properties")) { - CompoundTag propertiesTag = stateTag.getCompoundTag("Properties"); - for (Entry> property : propertiesTag) { - properties.put(property.getKey().toLowerCase(), ((StringTag) property.getValue()).getValue().toLowerCase()); - } - } - - palette[i] = new BlockState(id, properties); - } - } else { - this.palette = new BlockState[0]; - } - - 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 - ); - } - } - - */ - -} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/ChunkAnvil115.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/ChunkAnvil115.java deleted file mode 100644 index 6024d659..00000000 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/ChunkAnvil115.java +++ /dev/null @@ -1,257 +0,0 @@ -/* - * This file is part of BlueMap, licensed under the MIT License (MIT). - * - * Copyright (c) Blue (Lukas Rieger) - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package de.bluecolored.bluemap.core.mca; - -@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) 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 paletteTag = (ListTag) sectionData.getListTag("Palette"); - if (paletteTag != null) { - this.palette = new BlockState[paletteTag.size()]; - for (int i = 0; i < this.palette.length; i++) { - CompoundTag stateTag = paletteTag.get(i); - - String id = stateTag.getString("Name"); //shortcut to save time and memory - if (id.equals(AIR_ID)) { - palette[i] = BlockState.AIR; - continue; - } - - Map properties = new HashMap<>(); - - if (stateTag.containsKey("Properties")) { - CompoundTag propertiesTag = stateTag.getCompoundTag("Properties"); - for (Entry> property : propertiesTag) { - properties.put(property.getKey().toLowerCase(), ((StringTag) property.getValue()).getValue().toLowerCase()); - } - } - - palette[i] = new BlockState(id, properties); - } - } else { - this.palette = new BlockState[0]; - } - - 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 - ); - } - } - - */ - -} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/ChunkAnvil116.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/ChunkAnvil116.java deleted file mode 100644 index 76b7e05a..00000000 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/ChunkAnvil116.java +++ /dev/null @@ -1,239 +0,0 @@ -/* - * This file is part of BlueMap, licensed under the MIT License (MIT). - * - * Copyright (c) Blue (Lukas Rieger) - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package de.bluecolored.bluemap.core.mca; - -import 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 - ); - } - } - -} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/ChunkAnvil118.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/ChunkAnvil118.java deleted file mode 100644 index a41c1f42..00000000 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/ChunkAnvil118.java +++ /dev/null @@ -1,247 +0,0 @@ -/* - * This file is part of BlueMap, licensed under the MIT License (MIT). - * - * Copyright (c) Blue (Lukas Rieger) - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package de.bluecolored.bluemap.core.mca; - -import 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); - } - -} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/LegacyBiomes.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/LegacyBiomes.java deleted file mode 100644 index 35330aa5..00000000 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/LegacyBiomes.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * This file is part of BlueMap, licensed under the MIT License (MIT). - * - * Copyright (c) Blue (Lukas Rieger) - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package de.bluecolored.bluemap.core.mca; - -import java.util.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]; - } - -} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/MCAChunk.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/MCAChunk.java deleted file mode 100644 index 6e22e2db..00000000 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/MCAChunk.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * This file is part of BlueMap, licensed under the MIT License (MIT). - * - * Copyright (c) Blue (Lukas Rieger) - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package de.bluecolored.bluemap.core.mca; - -import 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() + - '}'; - } - -} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/MCAWorld.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/MCAWorld.java deleted file mode 100644 index 143966f9..00000000 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/MCAWorld.java +++ /dev/null @@ -1,285 +0,0 @@ -/* - * This file is part of BlueMap, licensed under the MIT License (MIT). - * - * Copyright (c) Blue (Lukas Rieger) - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package de.bluecolored.bluemap.core.mca; - -import 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 regionCache; - private final LoadingCache 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 listRegions() { - File[] regionFiles = getRegionFolder().toFile().listFiles(); - if (regionFiles == null) return Collections.emptyList(); - - List 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; - } - -} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/data/BiomesData.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/data/BiomesData.java deleted file mode 100644 index 654dd0a3..00000000 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/data/BiomesData.java +++ /dev/null @@ -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; - -} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/data/BlockStatesData.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/data/BlockStatesData.java deleted file mode 100644 index eecc3f57..00000000 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/data/BlockStatesData.java +++ /dev/null @@ -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; - -} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/data/ChunkData.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/data/ChunkData.java deleted file mode 100644 index 8d94b3ac..00000000 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/data/ChunkData.java +++ /dev/null @@ -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; - -} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/data/HeightmapsData.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/data/HeightmapsData.java deleted file mode 100644 index f55ce013..00000000 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/data/HeightmapsData.java +++ /dev/null @@ -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; - -} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/data/LevelData.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/data/LevelData.java deleted file mode 100644 index cf9fe59f..00000000 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/data/LevelData.java +++ /dev/null @@ -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; - - } - -} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/data/SectionData.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/data/SectionData.java deleted file mode 100644 index 8febf1a4..00000000 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/data/SectionData.java +++ /dev/null @@ -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; - -} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/region/LinearRegion.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/region/LinearRegion.java deleted file mode 100644 index ead1c2df..00000000 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/region/LinearRegion.java +++ /dev/null @@ -1,209 +0,0 @@ -/* - * This file is part of BlueMap, licensed under the MIT License (MIT). - * - * Copyright (c) Blue (Lukas Rieger) - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package de.bluecolored.bluemap.core.mca.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 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 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 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; - } - -} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/region/MCARegion.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/region/MCARegion.java deleted file mode 100644 index cfdfc674..00000000 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/region/MCARegion.java +++ /dev/null @@ -1,162 +0,0 @@ -/* - * This file is part of BlueMap, licensed under the MIT License (MIT). - * - * Copyright (c) Blue (Lukas Rieger) - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package de.bluecolored.bluemap.core.mca.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 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 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; - } - -} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/resourcepack/Dimension.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/resourcepack/Dimension.java deleted file mode 100644 index cf050a19..00000000 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/resourcepack/Dimension.java +++ /dev/null @@ -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"; - -} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/resourcepack/DimensionType.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/resourcepack/DimensionType.java deleted file mode 100644 index d3df1f97..00000000 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/resourcepack/DimensionType.java +++ /dev/null @@ -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"; - -} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/BlockColorCalculatorFactory.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/BlockColorCalculatorFactory.java index 1719f2c0..804f3be5 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/BlockColorCalculatorFactory.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/BlockColorCalculatorFactory.java @@ -29,7 +29,7 @@ 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; diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/ResourcePath.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/ResourcePath.java index a5db0970..877628b1 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/ResourcePath.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/ResourcePath.java @@ -77,9 +77,6 @@ private static String parsePath(Path filePath) { 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(), "/"); diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/adapter/ResourcesGson.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/adapter/ResourcesGson.java index 36f2ee67..5dfc46e1 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/adapter/ResourcesGson.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/adapter/ResourcesGson.java @@ -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 static GsonBuilder addAdapter(GsonBuilder builder) { ); } - public static String nextStringOrBoolean(JsonReader in) throws IOException { - if (in.peek() == JsonToken.BOOLEAN) return Boolean.toString(in.nextBoolean()); - return in.nextString(); - } - } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/biome/datapack/DpBiome.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/biome/datapack/DpBiome.java index 2bfcdb0e..601b0700 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/biome/datapack/DpBiome.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/biome/datapack/DpBiome.java @@ -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 Biome createBiome(String formatted) { ); } - public DpBiomeEffects getEffects() { - return effects; - } - - public double getTemperature() { - return temperature; - } - - public double getDownfall() { - return downfall; - } - } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/biome/datapack/DpBiomeEffects.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/biome/datapack/DpBiomeEffects.java index 9cf67379..807b80b6 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/biome/datapack/DpBiomeEffects.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/biome/datapack/DpBiomeEffects.java @@ -26,24 +26,14 @@ 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(); } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/datapack/DataPack.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/datapack/DataPack.java new file mode 100644 index 00000000..8c98f5a6 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/datapack/DataPack.java @@ -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 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 void loadResource(Path root, Path file, Loader loader, Map resultMap) { + try { + ResourcePath 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 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 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 load() throws IOException; + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/datapack/dimension/DimensionTypeData.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/datapack/dimension/DimensionTypeData.java new file mode 100644 index 00000000..a7618d5a --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/datapack/dimension/DimensionTypeData.java @@ -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; + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/resourcepack/ResourcePack.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/resourcepack/ResourcePack.java index bdc13a30..e6a5f911 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/resourcepack/ResourcePack.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/resourcepack/ResourcePack.java @@ -299,6 +299,7 @@ private void loadResources(Path root) throws IOException { }, BlueMap.THREAD_POOL), // load biome configs + // TODO: move this to datapacks? CompletableFuture.runAsync(() -> { list(root.resolve("assets")) .map(path -> path.resolve("biomes.json")) diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/resourcepack/blockstate/Multipart.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/resourcepack/blockstate/Multipart.java index bafa6829..bdcc2dc3 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/resourcepack/blockstate/Multipart.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/resourcepack/blockstate/Multipart.java @@ -27,9 +27,9 @@ 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 BlockStateCondition readCondition(JsonReader in) throws IOException { 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 BlockStateCondition readCondition(JsonReader in) throws IOException { 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(); + } + } } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/CompressedInputStream.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/CompressedInputStream.java index fa2df5cf..86063a0c 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/CompressedInputStream.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/CompressedInputStream.java @@ -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 Compression getCompression() { 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(); - } - } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/sql/PostgreSQLStorage.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/sql/PostgreSQLStorage.java index 7ac45c94..12035021 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/sql/PostgreSQLStorage.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/sql/PostgreSQLStorage.java @@ -29,7 +29,7 @@ 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 PostgreSQLStorage(Dialect dialect, SQLStorageSettings config) throws Malf 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 OutputStream writeMapTile(String mapId, int lod, Vector2i tile) throws IO @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(), diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/sql/SQLStorage.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/sql/SQLStorage.java index 3af604ac..7b050446 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/sql/SQLStorage.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/sql/SQLStorage.java @@ -32,7 +32,7 @@ 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 OutputStream writeMapTile(String mapId, int lod, Vector2i tile) throws IO 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 void deleteMapTile(String mapId, int lod, Vector2i tile) throws IOExcepti @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 -> { diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/FileHelper.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/FileHelper.java index 174c8a3e..0ce497ce 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/FileHelper.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/FileHelper.java @@ -40,7 +40,7 @@ public static OutputStream createFilepartOutputStream(final Path file) throws IO 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); diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/Grid.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/Grid.java similarity index 99% rename from BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/Grid.java rename to BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/Grid.java index 6d9777de..83fcd799 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/Grid.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/Grid.java @@ -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; diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/Key.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/Key.java index 9be2d1ec..3f255cfe 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/Key.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/Key.java @@ -73,7 +73,7 @@ public boolean equals(Object o) { 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 diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/WrappedInputStream.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/OnCloseInputStream.java similarity index 81% rename from BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/WrappedInputStream.java rename to BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/OnCloseInputStream.java index 46b772e0..f8fe29ad 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/WrappedInputStream.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/OnCloseInputStream.java @@ -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; diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/WrappedOutputStream.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/OnCloseOutputStream.java similarity index 78% rename from BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/WrappedOutputStream.java rename to BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/OnCloseOutputStream.java index 80d9f946..312496e0 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/WrappedOutputStream.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/OnCloseOutputStream.java @@ -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; diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/Chunk.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/Chunk.java index 2639b8b0..86b75df4 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/Chunk.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/Chunk.java @@ -26,22 +26,50 @@ 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; } } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/ChunkConsumer.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/ChunkConsumer.java new file mode 100644 index 00000000..9fe0c3cf --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/ChunkConsumer.java @@ -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."); + } + + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/DimensionType.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/DimensionType.java new file mode 100644 index 00000000..1f4592d0 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/DimensionType.java @@ -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; + + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/Region.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/Region.java index def3e4fd..01da07c6 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/Region.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/Region.java @@ -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.
- * (Be aware that the collection is not cached and recollected each time from the world-files!) + * Directly loads and returns the specified chunk.
+ * (implementations should consider overriding this method for a faster implementation) */ - default Collection 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.
- * (Be aware that the collection is not cached and recollected each time from the world-files!) + * Iterates over all chunks in this region and first calls {@link ChunkConsumer#filter(int, int, long)}.
+ * And if (any only if) that method returned true, 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 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; } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/World.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/World.java index e9b650f2..99537e52 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/World.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/World.java @@ -26,10 +26,11 @@ 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
@@ -38,17 +39,15 @@ */ 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 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 */ diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/Block.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/block/Block.java similarity index 94% rename from BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/Block.java rename to BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/block/Block.java index a3b9b14d..cb59ba68 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/Block.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/block/Block.java @@ -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> { @@ -131,7 +136,7 @@ public int getZ() { } public Chunk getChunk() { - if (chunk == null) chunk = world.getChunkAtBlock(x, y, z); + if (chunk == null) chunk = world.getChunkAtBlock(x, z); return chunk; } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/BlockNeighborhood.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/block/BlockNeighborhood.java similarity index 97% rename from BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/BlockNeighborhood.java rename to BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/block/BlockNeighborhood.java index 930f1548..ec9fc793 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/BlockNeighborhood.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/block/BlockNeighborhood.java @@ -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> extends ExtendedBlock { diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/ExtendedBlock.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/block/ExtendedBlock.java similarity index 82% rename from BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/ExtendedBlock.java rename to BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/block/ExtendedBlock.java index 5e7e2009..a021ac39 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/ExtendedBlock.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/block/ExtendedBlock.java @@ -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> extends Block { 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 @@ protected void reset() { this.biome = null; this.insideRenderBoundsCalculated = false; + this.isCaveCalculated = false; } @Override @@ -62,7 +65,7 @@ public BlockState getBlockState() { @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 boolean isInsideRenderBounds() { 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; } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/MCAMath.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/MCAUtil.java similarity index 84% rename from BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/MCAMath.java rename to BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/MCAUtil.java index 42806dbb..9c1a616c 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/MCAMath.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/MCAUtil.java @@ -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 static long getValueFromLongArray(long[] data, int valueIndex, int bitsPe /** * 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; } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/MCAWorld.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/MCAWorld.java new file mode 100644 index 00000000..6a98f16d --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/MCAWorld.java @@ -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 regionCache = Caffeine.newBuilder() + .executor(BlueMap.THREAD_POOL) + .maximumSize(64) + .expireAfterWrite(10, TimeUnit.MINUTES) + .build(this::loadRegion); + private final LoadingCache 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 listRegions() { + File[] regionFiles = getRegionFolder().toFile().listFiles(); + if (regionFiles == null) return Collections.emptyList(); + + List 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 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()); + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/PackedIntArrayAccess.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/PackedIntArrayAccess.java similarity index 68% rename from BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/PackedIntArrayAccess.java rename to BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/PackedIntArrayAccess.java index be85809a..b56432dc 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/PackedIntArrayAccess.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/PackedIntArrayAccess.java @@ -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; + } + +} \ No newline at end of file diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/chunk/ChunkLoader.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/chunk/ChunkLoader.java new file mode 100644 index 00000000..27ab17c9 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/chunk/ChunkLoader.java @@ -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> 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 { + + private final Class dataType; + private final BiFunction 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; + } + + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/chunk/Chunk_1_18.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/chunk/Chunk_1_18.java new file mode 100644 index 00000000..1d53101e --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/chunk/Chunk_1_18.java @@ -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; + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/chunk/MCAChunk.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/chunk/MCAChunk.java new file mode 100644 index 00000000..7a9e78b5 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/chunk/MCAChunk.java @@ -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; + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/deserializer/BlockStateDeserializer.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/data/BlockStateDeserializer.java similarity index 51% rename from BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/deserializer/BlockStateDeserializer.java rename to BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/data/BlockStateDeserializer.java index 13fdff7a..ae0e39a5 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/deserializer/BlockStateDeserializer.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/data/BlockStateDeserializer.java @@ -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 BlockState read(NBTReader reader) throws IOException { Map 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); } -} +} \ No newline at end of file diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/data/KeyDeserializer.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/data/KeyDeserializer.java new file mode 100644 index 00000000..951b7b49 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/data/KeyDeserializer.java @@ -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 { + + @Override + public Key read(NBTReader reader) throws IOException { + return new Key(reader.nextString()); + } + +} \ No newline at end of file diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/data/LevelData.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/data/LevelData.java new file mode 100644 index 00000000..7da02e17 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/data/LevelData.java @@ -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 dimensions = new HashMap<>(); + } + + @Getter + public static class Dimension { + private String type = "minecraft:overworld"; + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/region/MCARegion.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/region/MCARegion.java new file mode 100644 index 00000000..0eaf7dcd --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/region/MCARegion.java @@ -0,0 +1,188 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package de.bluecolored.bluemap.core.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; + } + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/region/RegionType.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/region/RegionType.java similarity index 94% rename from BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/region/RegionType.java rename to BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/region/RegionType.java index 63896faa..434da5f0 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/region/RegionType.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/region/RegionType.java @@ -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 @@ 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(); diff --git a/BlueMapCore/src/test/java/de/bluecolored/bluemap/core/world/GridTest.java b/BlueMapCore/src/test/java/de/bluecolored/bluemap/core/world/GridTest.java index 4eed1307..85eb1524 100644 --- a/BlueMapCore/src/test/java/de/bluecolored/bluemap/core/world/GridTest.java +++ b/BlueMapCore/src/test/java/de/bluecolored/bluemap/core/world/GridTest.java @@ -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; diff --git a/implementations/fabric-1.20/build.gradle.kts b/implementations/fabric-1.20/build.gradle.kts index cb0a61d1..15046fa0 100644 --- a/implementations/fabric-1.20/build.gradle.kts +++ b/implementations/fabric-1.20/build.gradle.kts @@ -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")