Core rewrite done
This commit is contained in:
parent
069e1747fa
commit
32311faccb
|
@ -42,11 +42,12 @@ import de.bluecolored.bluemap.core.MinecraftVersion;
|
|||
import de.bluecolored.bluemap.core.debug.StateDumper;
|
||||
import de.bluecolored.bluemap.core.logger.Logger;
|
||||
import de.bluecolored.bluemap.core.map.BmMap;
|
||||
import de.bluecolored.bluemap.core.mca.MCAWorld;
|
||||
import de.bluecolored.bluemap.core.resources.resourcepack.ResourcePack;
|
||||
import de.bluecolored.bluemap.core.storage.Storage;
|
||||
import de.bluecolored.bluemap.core.util.FileHelper;
|
||||
import de.bluecolored.bluemap.core.util.Key;
|
||||
import de.bluecolored.bluemap.core.world.World;
|
||||
import de.bluecolored.bluemap.core.world.mca.MCAWorld;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.spongepowered.configurate.ConfigurateException;
|
||||
|
@ -241,7 +242,7 @@ public class BlueMapService implements Closeable {
|
|||
if (world == null) {
|
||||
try {
|
||||
Logger.global.logInfo("Loading world '" + worldId + "' (" + worldFolder.toAbsolutePath().normalize() + ")...");
|
||||
world = new MCAWorld(worldFolder, mapConfig.getWorldSkyLight(), mapConfig.isIgnoreMissingLightData());
|
||||
world = MCAWorld.load(worldFolder, new Key("overworld")); //TODO
|
||||
worlds.put(worldId, world);
|
||||
} catch (IOException ex) {
|
||||
throw new ConfigurationException(
|
||||
|
|
|
@ -24,6 +24,8 @@
|
|||
*/
|
||||
package de.bluecolored.bluemap.common;
|
||||
|
||||
import com.google.gson.FieldNamingPolicy;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import de.bluecolored.bluemap.common.config.WebappConfig;
|
||||
import de.bluecolored.bluemap.core.BlueMap;
|
||||
|
@ -46,6 +48,11 @@ import java.util.zip.ZipFile;
|
|||
|
||||
public class WebFilesManager {
|
||||
|
||||
private static final Gson GSON = ResourcesGson.addAdapter(new GsonBuilder())
|
||||
.setFieldNamingPolicy(FieldNamingPolicy.IDENTITY)
|
||||
.setPrettyPrinting() // enable pretty printing for easy editing
|
||||
.create();
|
||||
|
||||
private final Path webRoot;
|
||||
private Settings settings;
|
||||
|
||||
|
@ -60,7 +67,7 @@ public class WebFilesManager {
|
|||
|
||||
public void loadSettings() throws IOException {
|
||||
try (BufferedReader reader = Files.newBufferedReader(getSettingsFile())) {
|
||||
this.settings = ResourcesGson.INSTANCE.fromJson(reader, Settings.class);
|
||||
this.settings = GSON.fromJson(reader, Settings.class);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -68,10 +75,7 @@ public class WebFilesManager {
|
|||
FileHelper.createDirectories(getSettingsFile().getParent());
|
||||
try (BufferedWriter writer = Files.newBufferedWriter(getSettingsFile(),
|
||||
StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)) {
|
||||
ResourcesGson.addAdapter(new GsonBuilder())
|
||||
.setPrettyPrinting() // enable pretty printing for easy editing
|
||||
.create()
|
||||
.toJson(this.settings, writer);
|
||||
GSON.toJson(this.settings, writer);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -22,51 +22,27 @@
|
|||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
package de.bluecolored.bluemap.core.world;
|
||||
package de.bluecolored.bluemap.common.config.typeserializer;
|
||||
|
||||
public class EmptyChunk implements Chunk {
|
||||
import de.bluecolored.bluemap.core.util.Key;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.spongepowered.configurate.ConfigurationNode;
|
||||
import org.spongepowered.configurate.serialize.SerializationException;
|
||||
import org.spongepowered.configurate.serialize.TypeSerializer;
|
||||
|
||||
public static final Chunk INSTANCE = new EmptyChunk();
|
||||
import java.lang.reflect.Type;
|
||||
|
||||
public class KeyTypeSerializer implements TypeSerializer<Key> {
|
||||
|
||||
@Override
|
||||
public boolean isGenerated() {
|
||||
return false;
|
||||
public Key deserialize(Type type, ConfigurationNode node) throws SerializationException {
|
||||
String formatted = node.getString();
|
||||
return formatted != null ? new Key(node.getString()) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getInhabitedTime() {
|
||||
return 0;
|
||||
public void serialize(Type type, @Nullable Key obj, ConfigurationNode node) throws SerializationException {
|
||||
if (obj != null) node.set(obj.getFormatted());
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockState getBlockState(int x, int y, int z) {
|
||||
return BlockState.AIR;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LightData getLightData(int x, int y, int z, LightData target) {
|
||||
return target.set(0, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBiome(int x, int y, int z) {
|
||||
return Biome.DEFAULT.getFormatted();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxY(int x, int z) {
|
||||
return 255;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMinY(int x, int z) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getWorldSurfaceY(int x, int z) { return 0; }
|
||||
|
||||
@Override
|
||||
public int getOceanFloorY(int x, int z) { return 0; }
|
||||
|
||||
}
|
|
@ -53,7 +53,7 @@ import de.bluecolored.bluemap.core.logger.Logger;
|
|||
import de.bluecolored.bluemap.core.map.BmMap;
|
||||
import de.bluecolored.bluemap.core.map.MapRenderState;
|
||||
import de.bluecolored.bluemap.core.storage.Storage;
|
||||
import de.bluecolored.bluemap.core.world.Block;
|
||||
import de.bluecolored.bluemap.core.world.block.Block;
|
||||
import de.bluecolored.bluemap.core.world.World;
|
||||
|
||||
import java.io.IOException;
|
||||
|
@ -714,14 +714,14 @@ public class Commands<S> {
|
|||
try {
|
||||
List<BmMap> maps = new ArrayList<>();
|
||||
if (worldToRender != null) {
|
||||
var world = plugin.getServerInterface().getWorld(worldToRender.getSaveFolder()).orElse(null);
|
||||
var world = plugin.getServerInterface().getWorld(worldToRender.getWorldFolder()).orElse(null);
|
||||
if (world != null) world.persistWorldChanges();
|
||||
|
||||
for (BmMap map : plugin.getMaps().values()) {
|
||||
if (map.getWorld().getSaveFolder().equals(worldToRender.getSaveFolder())) maps.add(map);
|
||||
if (map.getWorld().getWorldFolder().equals(worldToRender.getWorldFolder())) maps.add(map);
|
||||
}
|
||||
} else {
|
||||
var world = plugin.getServerInterface().getWorld(mapToRender.getWorld().getSaveFolder()).orElse(null);
|
||||
var world = plugin.getServerInterface().getWorld(mapToRender.getWorld().getWorldFolder()).orElse(null);
|
||||
if (world != null) world.persistWorldChanges();
|
||||
|
||||
maps.add(mapToRender);
|
||||
|
@ -832,7 +832,7 @@ public class Commands<S> {
|
|||
|
||||
source.sendMessage(Text.of(TextColor.BLUE, "Worlds loaded by BlueMap:"));
|
||||
for (var entry : plugin.getWorlds().entrySet()) {
|
||||
source.sendMessage(Text.of(TextColor.GRAY, " - ", TextColor.WHITE, entry.getValue().getName()).setHoverText(Text.of(entry.getValue().getSaveFolder(), TextColor.GRAY, " (" + entry.getKey() + ")")));
|
||||
source.sendMessage(Text.of(TextColor.GRAY, " - ", TextColor.WHITE, entry.getValue().getName()).setHoverText(Text.of(entry.getValue().getWorldFolder(), TextColor.GRAY, " (" + entry.getKey() + ")")));
|
||||
}
|
||||
|
||||
return 1;
|
||||
|
|
|
@ -27,7 +27,7 @@ package de.bluecolored.bluemap.common.rendermanager;
|
|||
import com.flowpowered.math.vector.Vector2i;
|
||||
import de.bluecolored.bluemap.api.debug.DebugDump;
|
||||
import de.bluecolored.bluemap.core.map.BmMap;
|
||||
import de.bluecolored.bluemap.core.world.Grid;
|
||||
import de.bluecolored.bluemap.core.util.Grid;
|
||||
import de.bluecolored.bluemap.core.world.World;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
|
|
@ -27,11 +27,14 @@ package de.bluecolored.bluemap.common.rendermanager;
|
|||
import com.flowpowered.math.vector.Vector2i;
|
||||
import com.flowpowered.math.vector.Vector2l;
|
||||
import de.bluecolored.bluemap.api.debug.DebugDump;
|
||||
import de.bluecolored.bluemap.core.logger.Logger;
|
||||
import de.bluecolored.bluemap.core.map.BmMap;
|
||||
import de.bluecolored.bluemap.core.util.Grid;
|
||||
import de.bluecolored.bluemap.core.world.Chunk;
|
||||
import de.bluecolored.bluemap.core.world.Grid;
|
||||
import de.bluecolored.bluemap.core.world.ChunkConsumer;
|
||||
import de.bluecolored.bluemap.core.world.Region;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
@ -71,13 +74,17 @@ public class WorldRegionRenderTask implements RenderTask {
|
|||
Set<Vector2l> tileSet = new HashSet<>();
|
||||
startTime = System.currentTimeMillis();
|
||||
|
||||
//Logger.global.logInfo("Starting: " + worldRegion);
|
||||
|
||||
long changesSince = 0;
|
||||
if (!force) changesSince = map.getRenderState().getRenderTime(worldRegion);
|
||||
|
||||
// collect chunks
|
||||
long changesSince = force ? 0 : map.getRenderState().getRenderTime(worldRegion);
|
||||
Region region = map.getWorld().getRegion(worldRegion.getX(), worldRegion.getY());
|
||||
Collection<Vector2i> chunks = region.listChunks(changesSince);
|
||||
Collection<Vector2i> chunks = new ArrayList<>(1024);
|
||||
try {
|
||||
region.iterateAllChunks((ChunkConsumer.ListOnly) (x, z, timestamp) -> {
|
||||
if (timestamp >= changesSince) chunks.add(new Vector2i(x, z));
|
||||
});
|
||||
} catch (IOException ex) {
|
||||
Logger.global.logWarning("Failed to read region " + worldRegion + " from world " + map.getWorld().getWorldFolder() + " (" + ex + ")");
|
||||
}
|
||||
|
||||
Grid tileGrid = map.getHiresModelManager().getTileGrid();
|
||||
Grid chunkGrid = map.getWorld().getChunkGrid();
|
||||
|
@ -115,6 +122,10 @@ public class WorldRegionRenderTask implements RenderTask {
|
|||
.collect(Collectors.toCollection(ArrayDeque::new));
|
||||
|
||||
if (tiles.isEmpty()) complete();
|
||||
else {
|
||||
// preload chunks
|
||||
map.getWorld().preloadRegionChunks(worldRegion.getX(), worldRegion.getY());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -132,7 +143,6 @@ public class WorldRegionRenderTask implements RenderTask {
|
|||
this.atWork++;
|
||||
}
|
||||
|
||||
//Logger.global.logInfo("Working on " + worldRegion + " - Tile " + tile);
|
||||
if (tileRenderPreconditions(tile)) {
|
||||
map.renderTile(tile); // <- actual work
|
||||
}
|
||||
|
@ -163,6 +173,7 @@ public class WorldRegionRenderTask implements RenderTask {
|
|||
for (int z = minChunk.getY(); z <= maxChunk.getY(); z++) {
|
||||
Chunk chunk = map.getWorld().getChunk(x, z);
|
||||
if (!chunk.isGenerated()) return false;
|
||||
if (!chunk.hasLightData() && !map.getMapSettings().isIgnoreMissingLightData()) return false;
|
||||
if (chunk.getInhabitedTime() >= minInhab) isInhabited = true;
|
||||
}
|
||||
}
|
||||
|
@ -184,8 +195,6 @@ public class WorldRegionRenderTask implements RenderTask {
|
|||
|
||||
private void complete() {
|
||||
map.getRenderState().setRenderTime(worldRegion, startTime);
|
||||
|
||||
//Logger.global.logInfo("Done with: " + worldRegion);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -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")
|
||||
|
||||
|
|
|
@ -25,6 +25,8 @@
|
|||
package de.bluecolored.bluemap.core.map;
|
||||
|
||||
import com.flowpowered.math.vector.Vector2i;
|
||||
import com.google.gson.FieldNamingPolicy;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import de.bluecolored.bluemap.api.debug.DebugDump;
|
||||
import de.bluecolored.bluemap.api.gson.MarkerGson;
|
||||
|
@ -35,7 +37,7 @@ import de.bluecolored.bluemap.core.map.lowres.LowresTileManager;
|
|||
import de.bluecolored.bluemap.core.resources.adapter.ResourcesGson;
|
||||
import de.bluecolored.bluemap.core.resources.resourcepack.ResourcePack;
|
||||
import de.bluecolored.bluemap.core.storage.Storage;
|
||||
import de.bluecolored.bluemap.core.world.Grid;
|
||||
import de.bluecolored.bluemap.core.util.Grid;
|
||||
import de.bluecolored.bluemap.core.world.World;
|
||||
|
||||
import java.io.*;
|
||||
|
@ -55,6 +57,11 @@ public class BmMap {
|
|||
public static final String META_FILE_MARKERS = "live/markers.json";
|
||||
public static final String META_FILE_PLAYERS = "live/players.json";
|
||||
|
||||
private static final Gson GSON = ResourcesGson.addAdapter(new GsonBuilder())
|
||||
.setFieldNamingPolicy(FieldNamingPolicy.IDENTITY)
|
||||
.registerTypeAdapter(BmMap.class, new MapSettingsSerializer())
|
||||
.create();
|
||||
|
||||
private final String id;
|
||||
private final String name;
|
||||
private final String worldId;
|
||||
|
@ -197,10 +204,7 @@ public class BmMap {
|
|||
OutputStream out = storage.writeMeta(id, META_FILE_SETTINGS);
|
||||
Writer writer = new OutputStreamWriter(out, StandardCharsets.UTF_8)
|
||||
) {
|
||||
ResourcesGson.addAdapter(new GsonBuilder())
|
||||
.registerTypeAdapter(BmMap.class, new MapSettingsSerializer())
|
||||
.create()
|
||||
.toJson(this, writer);
|
||||
GSON.toJson(this, writer);
|
||||
} catch (Exception ex) {
|
||||
Logger.global.logError("Failed to save settings for map '" + getId() + "'!", ex);
|
||||
}
|
||||
|
|
|
@ -24,6 +24,9 @@
|
|||
*/
|
||||
package de.bluecolored.bluemap.core.map;
|
||||
|
||||
import com.google.gson.FieldNamingPolicy;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonIOException;
|
||||
import de.bluecolored.bluemap.api.debug.DebugDump;
|
||||
import de.bluecolored.bluemap.core.resources.ResourcePath;
|
||||
|
@ -40,6 +43,10 @@ import java.util.Map;
|
|||
@DebugDump
|
||||
public class TextureGallery {
|
||||
|
||||
private static final Gson GSON = ResourcesGson.addAdapter(new GsonBuilder())
|
||||
.setFieldNamingPolicy(FieldNamingPolicy.IDENTITY)
|
||||
.create();
|
||||
|
||||
private final Map<ResourcePath<Texture>, Integer> ordinalMap;
|
||||
private int nextId;
|
||||
|
||||
|
@ -83,7 +90,7 @@ public class TextureGallery {
|
|||
});
|
||||
|
||||
try (Writer writer = new OutputStreamWriter(out)) {
|
||||
ResourcesGson.INSTANCE.toJson(textures, Texture[].class, writer);
|
||||
GSON.toJson(textures, Texture[].class, writer);
|
||||
} catch (JsonIOException ex) {
|
||||
throw new IOException(ex);
|
||||
}
|
||||
|
@ -92,7 +99,7 @@ public class TextureGallery {
|
|||
public static TextureGallery readTexturesFile(InputStream in) throws IOException {
|
||||
TextureGallery gallery = new TextureGallery();
|
||||
try (Reader reader = new InputStreamReader(in)) {
|
||||
Texture[] textures = ResourcesGson.INSTANCE.fromJson(reader, Texture[].class);
|
||||
Texture[] textures = GSON.fromJson(reader, Texture[].class);
|
||||
if (textures == null) throw new IOException("Texture data is empty!");
|
||||
gallery.nextId = textures.length;
|
||||
for (int ordinal = 0; ordinal < textures.length; ordinal++) {
|
||||
|
|
|
@ -31,7 +31,7 @@ import de.bluecolored.bluemap.core.map.TextureGallery;
|
|||
import de.bluecolored.bluemap.core.map.TileMetaConsumer;
|
||||
import de.bluecolored.bluemap.core.resources.resourcepack.ResourcePack;
|
||||
import de.bluecolored.bluemap.core.storage.Storage;
|
||||
import de.bluecolored.bluemap.core.world.Grid;
|
||||
import de.bluecolored.bluemap.core.util.Grid;
|
||||
import de.bluecolored.bluemap.core.world.World;
|
||||
|
||||
import java.io.IOException;
|
||||
|
|
|
@ -30,7 +30,8 @@ import de.bluecolored.bluemap.core.map.TileMetaConsumer;
|
|||
import de.bluecolored.bluemap.core.map.hires.blockmodel.BlockStateModelFactory;
|
||||
import de.bluecolored.bluemap.core.resources.resourcepack.ResourcePack;
|
||||
import de.bluecolored.bluemap.core.util.math.Color;
|
||||
import de.bluecolored.bluemap.core.world.BlockNeighborhood;
|
||||
import de.bluecolored.bluemap.core.world.Chunk;
|
||||
import de.bluecolored.bluemap.core.world.block.BlockNeighborhood;
|
||||
import de.bluecolored.bluemap.core.world.World;
|
||||
|
||||
public class HiresModelRenderer {
|
||||
|
@ -73,8 +74,9 @@ public class HiresModelRenderer {
|
|||
columnColor.set(0, 0, 0, 0, true);
|
||||
|
||||
if (renderSettings.isInsideRenderBoundaries(x, z)) {
|
||||
minY = Math.max(min.getY(), world.getMinY(x, z));
|
||||
maxY = Math.min(max.getY(), world.getMaxY(x, z));
|
||||
Chunk chunk = world.getChunkAtBlock(x, z);
|
||||
minY = Math.max(min.getY(), chunk.getMinY(x, z));
|
||||
maxY = Math.min(max.getY(), chunk.getMaxY(x, z));
|
||||
|
||||
for (y = minY; y <= maxY; y++) {
|
||||
block.set(x, y, z);
|
||||
|
|
|
@ -78,6 +78,10 @@ public interface RenderSettings {
|
|||
return true;
|
||||
}
|
||||
|
||||
default boolean isIgnoreMissingLightData() {
|
||||
return false;
|
||||
}
|
||||
|
||||
default boolean isInsideRenderBoundaries(int x, int z) {
|
||||
Vector3i min = getMinPos();
|
||||
Vector3i max = getMaxPos();
|
||||
|
|
|
@ -31,7 +31,7 @@ import de.bluecolored.bluemap.core.resources.resourcepack.ResourcePack;
|
|||
import de.bluecolored.bluemap.core.resources.resourcepack.blockmodel.BlockModel;
|
||||
import de.bluecolored.bluemap.core.resources.resourcepack.blockstate.Variant;
|
||||
import de.bluecolored.bluemap.core.util.math.Color;
|
||||
import de.bluecolored.bluemap.core.world.BlockNeighborhood;
|
||||
import de.bluecolored.bluemap.core.world.block.BlockNeighborhood;
|
||||
import de.bluecolored.bluemap.core.world.BlockState;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
|
|
@ -42,9 +42,9 @@ import de.bluecolored.bluemap.core.util.math.Color;
|
|||
import de.bluecolored.bluemap.core.util.math.MatrixM3f;
|
||||
import de.bluecolored.bluemap.core.util.math.VectorM2f;
|
||||
import de.bluecolored.bluemap.core.util.math.VectorM3f;
|
||||
import de.bluecolored.bluemap.core.world.BlockNeighborhood;
|
||||
import de.bluecolored.bluemap.core.world.block.BlockNeighborhood;
|
||||
import de.bluecolored.bluemap.core.world.BlockState;
|
||||
import de.bluecolored.bluemap.core.world.ExtendedBlock;
|
||||
import de.bluecolored.bluemap.core.world.block.ExtendedBlock;
|
||||
|
||||
/**
|
||||
* A model builder for all liquid blocks
|
||||
|
@ -71,7 +71,6 @@ public class LiquidModelBuilder {
|
|||
private BlockModel modelResource;
|
||||
private BlockModelView blockModel;
|
||||
private Color blockColor;
|
||||
private boolean isCave;
|
||||
|
||||
public LiquidModelBuilder(ResourcePack resourcePack, TextureGallery textureGallery, RenderSettings renderSettings) {
|
||||
this.resourcePack = resourcePack;
|
||||
|
@ -100,17 +99,13 @@ public class LiquidModelBuilder {
|
|||
this.blockModel = blockModel;
|
||||
this.blockColor = color;
|
||||
|
||||
this.isCave =
|
||||
this.block.getY() < renderSettings.getRemoveCavesBelowY() &&
|
||||
this.block.getY() < block.getChunk().getOceanFloorY(block.getX(), block.getZ()) + renderSettings.getCaveDetectionOceanFloor();
|
||||
|
||||
build();
|
||||
}
|
||||
|
||||
private final Color tintcolor = new Color();
|
||||
private void build() {
|
||||
// filter out blocks that are in a "cave" that should not be rendered
|
||||
if (this.isCave && (renderSettings.isCaveDetectionUsesBlockLight() ? block.getBlockLightLevel() : block.getSunLightLevel()) == 0f) return;
|
||||
if (this.block.isCave() && (renderSettings.isCaveDetectionUsesBlockLight() ? block.getBlockLightLevel() : block.getSunLightLevel()) == 0f) return;
|
||||
|
||||
int level = blockState.getLiquidLevel();
|
||||
if (level < 8 && !(level == 0 && isSameLiquid(block.getNeighborBlock(0, 1, 0)))){
|
||||
|
|
|
@ -45,9 +45,9 @@ import de.bluecolored.bluemap.core.util.math.Color;
|
|||
import de.bluecolored.bluemap.core.util.math.MatrixM4f;
|
||||
import de.bluecolored.bluemap.core.util.math.VectorM2f;
|
||||
import de.bluecolored.bluemap.core.util.math.VectorM3f;
|
||||
import de.bluecolored.bluemap.core.world.BlockNeighborhood;
|
||||
import de.bluecolored.bluemap.core.world.block.BlockNeighborhood;
|
||||
import de.bluecolored.bluemap.core.world.BlockProperties;
|
||||
import de.bluecolored.bluemap.core.world.ExtendedBlock;
|
||||
import de.bluecolored.bluemap.core.world.block.ExtendedBlock;
|
||||
import de.bluecolored.bluemap.core.world.LightData;
|
||||
|
||||
/**
|
||||
|
@ -74,7 +74,6 @@ public class ResourceModelBuilder {
|
|||
private BlockModelView blockModel;
|
||||
private Color blockColor;
|
||||
private float blockColorOpacity;
|
||||
private boolean isCave;
|
||||
|
||||
public ResourceModelBuilder(ResourcePack resourcePack, TextureGallery textureGallery, RenderSettings renderSettings) {
|
||||
this.resourcePack = resourcePack;
|
||||
|
@ -95,10 +94,6 @@ public class ResourceModelBuilder {
|
|||
this.variant = variant;
|
||||
this.modelResource = variant.getModel().getResource();
|
||||
|
||||
this.isCave =
|
||||
this.block.getY() < renderSettings.getRemoveCavesBelowY() &&
|
||||
this.block.getY() < block.getChunk().getOceanFloorY(block.getX(), block.getZ()) + renderSettings.getCaveDetectionOceanFloor();
|
||||
|
||||
this.tintColor.set(0, 0, 0, -1, true);
|
||||
|
||||
// render model
|
||||
|
@ -201,7 +196,7 @@ public class ResourceModelBuilder {
|
|||
int blockLight = Math.max(blockLightData.getBlockLight(), facedLightData.getBlockLight());
|
||||
|
||||
// filter out faces that are in a "cave" that should not be rendered
|
||||
if (isCave && (renderSettings.isCaveDetectionUsesBlockLight() ? Math.max(blockLight, sunLight) : sunLight) == 0f) return;
|
||||
if (block.isCave() && (renderSettings.isCaveDetectionUsesBlockLight() ? Math.max(blockLight, sunLight) : sunLight) == 0f) return;
|
||||
|
||||
// initialize the faces
|
||||
blockModel.initialize();
|
||||
|
|
|
@ -31,7 +31,7 @@ import de.bluecolored.bluemap.core.logger.Logger;
|
|||
import de.bluecolored.bluemap.core.storage.Storage;
|
||||
import de.bluecolored.bluemap.core.util.Vector2iCache;
|
||||
import de.bluecolored.bluemap.core.util.math.Color;
|
||||
import de.bluecolored.bluemap.core.world.Grid;
|
||||
import de.bluecolored.bluemap.core.util.Grid;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@ package de.bluecolored.bluemap.core.map.lowres;
|
|||
import de.bluecolored.bluemap.core.map.TileMetaConsumer;
|
||||
import de.bluecolored.bluemap.core.storage.Storage;
|
||||
import de.bluecolored.bluemap.core.util.math.Color;
|
||||
import de.bluecolored.bluemap.core.world.Grid;
|
||||
import de.bluecolored.bluemap.core.util.Grid;
|
||||
|
||||
public class LowresTileManager implements TileMetaConsumer {
|
||||
|
||||
|
|
|
@ -1,252 +0,0 @@
|
|||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
package de.bluecolored.bluemap.core.mca;
|
||||
|
||||
@SuppressWarnings("FieldMayBeFinal")
|
||||
public class ChunkAnvil113 /* extends MCAChunk */ {
|
||||
private static final long[] EMPTY_LONG_ARRAY = new long[0];
|
||||
|
||||
/*
|
||||
|
||||
private boolean isGenerated;
|
||||
private boolean hasLight;
|
||||
private long inhabitedTime;
|
||||
private Section[] sections;
|
||||
private int[] biomes;
|
||||
|
||||
private long[] oceanFloorHeights = EMPTY_LONG_ARRAY;
|
||||
private long[] worldSurfaceHeights = EMPTY_LONG_ARRAY;
|
||||
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public ChunkAnvil113(MCAWorld world, CompoundTag chunkTag) {
|
||||
super(world, chunkTag);
|
||||
|
||||
CompoundTag levelData = chunkTag.getCompoundTag("Level");
|
||||
|
||||
String status = levelData.getString("Status");
|
||||
this.isGenerated = status.equals("full") ||
|
||||
status.equals("fullchunk") ||
|
||||
status.equals("postprocessed");
|
||||
this.hasLight = isGenerated;
|
||||
|
||||
this.inhabitedTime = levelData.getLong("InhabitedTime");
|
||||
|
||||
if (!isGenerated && getWorld().isIgnoreMissingLightData()) {
|
||||
isGenerated = !status.equals("empty");
|
||||
}
|
||||
|
||||
if (levelData.containsKey("Heightmaps")) {
|
||||
CompoundTag heightmapsTag = levelData.getCompoundTag("Heightmaps");
|
||||
this.worldSurfaceHeights = heightmapsTag.getLongArray("WORLD_SURFACE");
|
||||
this.oceanFloorHeights = heightmapsTag.getLongArray("OCEAN_FLOOR");
|
||||
}
|
||||
|
||||
sections = new Section[32]; //32 supports a max world-height of 512 which is the max that the hightmaps of Minecraft V1.13+ can store with 9 bits, i believe?
|
||||
if (levelData.containsKey("Sections")) {
|
||||
for (CompoundTag sectionTag : ((ListTag<CompoundTag>) levelData.getListTag("Sections"))) {
|
||||
Section section = new Section(sectionTag);
|
||||
if (section.getSectionY() >= 0 && section.getSectionY() < sections.length) sections[section.getSectionY()] = section;
|
||||
}
|
||||
} else {
|
||||
sections = new Section[0];
|
||||
}
|
||||
|
||||
Tag<?> tag = levelData.get("Biomes"); //tag can be byte-array or int-array
|
||||
if (tag instanceof ByteArrayTag) {
|
||||
byte[] bs = ((ByteArrayTag) tag).getValue();
|
||||
biomes = new int[bs.length];
|
||||
|
||||
for (int i = 0; i < bs.length; i++) {
|
||||
biomes[i] = bs[i] & 0xFF;
|
||||
}
|
||||
}
|
||||
else if (tag instanceof IntArrayTag) {
|
||||
biomes = ((IntArrayTag) tag).getValue();
|
||||
}
|
||||
|
||||
if (biomes == null || biomes.length == 0) {
|
||||
biomes = new int[256];
|
||||
}
|
||||
|
||||
if (biomes.length < 256) {
|
||||
biomes = Arrays.copyOf(biomes, 256);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isGenerated() {
|
||||
return isGenerated;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getInhabitedTime() {
|
||||
return inhabitedTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockState getBlockState(int x, int y, int z) {
|
||||
int sectionY = y >> 4;
|
||||
if (sectionY < 0 || sectionY >= this.sections.length) return BlockState.AIR;
|
||||
|
||||
Section section = this.sections[sectionY];
|
||||
if (section == null) return BlockState.AIR;
|
||||
|
||||
return section.getBlockState(x, y, z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LightData getLightData(int x, int y, int z, LightData target) {
|
||||
if (!hasLight) return target.set(getWorld().getSkyLight(), 0);
|
||||
|
||||
int sectionY = y >> 4;
|
||||
if (sectionY < 0 || sectionY >= this.sections.length)
|
||||
return (y < 0) ? target.set(0, 0) : target.set(getWorld().getSkyLight(), 0);
|
||||
|
||||
Section section = this.sections[sectionY];
|
||||
if (section == null) return target.set(getWorld().getSkyLight(), 0);
|
||||
|
||||
return section.getLightData(x, y, z, target);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBiome(int x, int y, int z) {
|
||||
x &= 0xF; z &= 0xF;
|
||||
int biomeIntIndex = z * 16 + x;
|
||||
|
||||
if (biomeIntIndex >= this.biomes.length) return Biome.DEFAULT.getFormatted();
|
||||
|
||||
return LegacyBiomes.idFor(biomes[biomeIntIndex]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getWorldSurfaceY(int x, int z) {
|
||||
if (this.worldSurfaceHeights.length < 36) return 0;
|
||||
|
||||
x &= 0xF; z &= 0xF;
|
||||
return (int) MCAMath.getValueFromLongStream(this.worldSurfaceHeights, z * 16 + x, 9);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOceanFloorY(int x, int z) {
|
||||
if (this.oceanFloorHeights.length < 36) return 0;
|
||||
|
||||
x &= 0xF; z &= 0xF;
|
||||
return (int) MCAMath.getValueFromLongStream(this.oceanFloorHeights, z * 16 + x, 9);
|
||||
}
|
||||
|
||||
private static class Section {
|
||||
private static final String AIR_ID = "minecraft:air";
|
||||
|
||||
private int sectionY;
|
||||
private byte[] blockLight;
|
||||
private byte[] skyLight;
|
||||
private long[] blocks;
|
||||
private BlockState[] palette;
|
||||
|
||||
private int bitsPerBlock;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public Section(CompoundTag sectionData) {
|
||||
this.sectionY = sectionData.get("Y", NumberTag.class).asInt();
|
||||
this.blockLight = sectionData.getByteArray("BlockLight");
|
||||
this.skyLight = sectionData.getByteArray("SkyLight");
|
||||
this.blocks = sectionData.getLongArray("BlockStates");
|
||||
|
||||
if (blocks.length < 256 && blocks.length > 0) blocks = Arrays.copyOf(blocks, 256);
|
||||
if (blockLight.length < 2048 && blockLight.length > 0) blockLight = Arrays.copyOf(blockLight, 2048);
|
||||
if (skyLight.length < 2048 && skyLight.length > 0) skyLight = Arrays.copyOf(skyLight, 2048);
|
||||
|
||||
//read block palette
|
||||
ListTag<CompoundTag> paletteTag = (ListTag<CompoundTag>) sectionData.getListTag("Palette");
|
||||
if (paletteTag != null) {
|
||||
this.palette = new BlockState[paletteTag.size()];
|
||||
for (int i = 0; i < this.palette.length; i++) {
|
||||
CompoundTag stateTag = paletteTag.get(i);
|
||||
|
||||
String id = stateTag.getString("Name"); //shortcut to save time and memory
|
||||
if (id.equals(AIR_ID)) {
|
||||
palette[i] = BlockState.AIR;
|
||||
continue;
|
||||
}
|
||||
|
||||
Map<String, String> properties = new HashMap<>();
|
||||
|
||||
if (stateTag.containsKey("Properties")) {
|
||||
CompoundTag propertiesTag = stateTag.getCompoundTag("Properties");
|
||||
for (Entry<String, Tag<?>> property : propertiesTag) {
|
||||
properties.put(property.getKey().toLowerCase(), ((StringTag) property.getValue()).getValue().toLowerCase());
|
||||
}
|
||||
}
|
||||
|
||||
palette[i] = new BlockState(id, properties);
|
||||
}
|
||||
} else {
|
||||
this.palette = new BlockState[0];
|
||||
}
|
||||
|
||||
this.bitsPerBlock = this.blocks.length >> 6; // available longs * 64 (bits per long) / 4096 (blocks per section) (floored result)
|
||||
}
|
||||
|
||||
public int getSectionY() {
|
||||
return sectionY;
|
||||
}
|
||||
|
||||
public BlockState getBlockState(int x, int y, int z) {
|
||||
if (palette.length == 1) return palette[0];
|
||||
if (blocks.length == 0) return BlockState.AIR;
|
||||
|
||||
x &= 0xF; y &= 0xF; z &= 0xF; // Math.floorMod(pos.getX(), 16)
|
||||
|
||||
int blockIndex = y * 256 + z * 16 + x;
|
||||
|
||||
long value = MCAMath.getValueFromLongStream(blocks, blockIndex, bitsPerBlock);
|
||||
if (value >= palette.length) {
|
||||
Logger.global.noFloodWarning("palettewarning", "Got palette value " + value + " but palette has size of " + palette.length + " (Future occasions of this error will not be logged)");
|
||||
return BlockState.MISSING;
|
||||
}
|
||||
|
||||
return palette[(int) value];
|
||||
}
|
||||
|
||||
public LightData getLightData(int x, int y, int z, LightData target) {
|
||||
if (blockLight.length == 0 && skyLight.length == 0) return target.set(0, 0);
|
||||
|
||||
x &= 0xF; y &= 0xF; z &= 0xF; // Math.floorMod(pos.getX(), 16)
|
||||
|
||||
int blockByteIndex = y * 256 + z * 16 + x;
|
||||
int blockHalfByteIndex = blockByteIndex >> 1; // blockByteIndex / 2
|
||||
boolean largeHalf = (blockByteIndex & 0x1) != 0; // (blockByteIndex % 2) == 0
|
||||
|
||||
return target.set(
|
||||
this.skyLight.length > 0 ? MCAMath.getByteHalf(this.skyLight[blockHalfByteIndex], largeHalf) : 0,
|
||||
this.blockLight.length > 0 ? MCAMath.getByteHalf(this.blockLight[blockHalfByteIndex], largeHalf) : 0
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
}
|
|
@ -1,257 +0,0 @@
|
|||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
package de.bluecolored.bluemap.core.mca;
|
||||
|
||||
@SuppressWarnings("FieldMayBeFinal")
|
||||
public class ChunkAnvil115 /* extends MCAChunk */ {
|
||||
private static final long[] EMPTY_LONG_ARRAY = new long[0];
|
||||
|
||||
/*
|
||||
|
||||
private boolean isGenerated;
|
||||
private boolean hasLight;
|
||||
private long inhabitedTime;
|
||||
private Section[] sections;
|
||||
private int[] biomes;
|
||||
|
||||
private long[] oceanFloorHeights = EMPTY_LONG_ARRAY;
|
||||
private long[] worldSurfaceHeights = EMPTY_LONG_ARRAY;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public ChunkAnvil115(MCAWorld world, CompoundTag chunkTag) {
|
||||
super(world, chunkTag);
|
||||
|
||||
CompoundTag levelData = chunkTag.getCompoundTag("Level");
|
||||
|
||||
String status = levelData.getString("Status");
|
||||
this.isGenerated = status.equals("full");
|
||||
this.hasLight = isGenerated;
|
||||
|
||||
this.inhabitedTime = levelData.getLong("InhabitedTime");
|
||||
|
||||
if (!isGenerated && getWorld().isIgnoreMissingLightData()) {
|
||||
isGenerated = !status.equals("empty");
|
||||
}
|
||||
|
||||
if (levelData.containsKey("Heightmaps")) {
|
||||
CompoundTag heightmapsTag = levelData.getCompoundTag("Heightmaps");
|
||||
this.worldSurfaceHeights = heightmapsTag.getLongArray("WORLD_SURFACE");
|
||||
this.oceanFloorHeights = heightmapsTag.getLongArray("OCEAN_FLOOR");
|
||||
}
|
||||
|
||||
sections = new Section[32]; //32 supports a max world-height of 512 which is the max that the hightmaps of Minecraft V1.13+ can store with 9 bits, i believe?
|
||||
if (levelData.containsKey("Sections")) {
|
||||
for (CompoundTag sectionTag : ((ListTag<CompoundTag>) levelData.getListTag("Sections"))) {
|
||||
Section section = new Section(sectionTag);
|
||||
if (section.getSectionY() >= 0 && section.getSectionY() < sections.length) sections[section.getSectionY()] = section;
|
||||
}
|
||||
} else {
|
||||
sections = new Section[0];
|
||||
}
|
||||
|
||||
Tag<?> tag = levelData.get("Biomes"); //tag can be byte-array or int-array
|
||||
if (tag instanceof ByteArrayTag) {
|
||||
byte[] bs = ((ByteArrayTag) tag).getValue();
|
||||
biomes = new int[bs.length];
|
||||
|
||||
for (int i = 0; i < bs.length; i++) {
|
||||
biomes[i] = bs[i] & 0xFF;
|
||||
}
|
||||
}
|
||||
else if (tag instanceof IntArrayTag) {
|
||||
biomes = ((IntArrayTag) tag).getValue();
|
||||
}
|
||||
|
||||
if (biomes == null || biomes.length == 0) {
|
||||
biomes = new int[1024];
|
||||
}
|
||||
|
||||
if (biomes.length < 1024) {
|
||||
biomes = Arrays.copyOf(biomes, 1024);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isGenerated() {
|
||||
return isGenerated;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getInhabitedTime() {
|
||||
return inhabitedTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockState getBlockState(int x, int y, int z) {
|
||||
int sectionY = y >> 4;
|
||||
if (sectionY < 0 || sectionY >= this.sections.length) return BlockState.AIR;
|
||||
|
||||
Section section = this.sections[sectionY];
|
||||
if (section == null) return BlockState.AIR;
|
||||
|
||||
return section.getBlockState(x, y, z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LightData getLightData(int x, int y, int z, LightData target) {
|
||||
if (!hasLight) return target.set(getWorld().getSkyLight(), 0);
|
||||
|
||||
int sectionY = y >> 4;
|
||||
if (sectionY < 0 || sectionY >= this.sections.length)
|
||||
return (y < 0) ? target.set(0, 0) : target.set(getWorld().getSkyLight(), 0);
|
||||
|
||||
Section section = this.sections[sectionY];
|
||||
if (section == null) return target.set(getWorld().getSkyLight(), 0);
|
||||
|
||||
return section.getLightData(x, y, z, target);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBiome(int x, int y, int z) {
|
||||
x = (x & 0xF) / 4; // Math.floorMod(pos.getX(), 16)
|
||||
z = (z & 0xF) / 4;
|
||||
y = y / 4;
|
||||
int biomeIntIndex = y * 16 + z * 4 + x;
|
||||
|
||||
if (biomeIntIndex < 0) return Biome.DEFAULT.getFormatted();
|
||||
if (biomeIntIndex >= this.biomes.length) return Biome.DEFAULT.getFormatted();
|
||||
|
||||
return LegacyBiomes.idFor(biomes[biomeIntIndex]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxY(int x, int z) {
|
||||
return sections.length * 16 + 15;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getWorldSurfaceY(int x, int z) {
|
||||
if (this.worldSurfaceHeights.length < 36) return 0;
|
||||
|
||||
x &= 0xF; z &= 0xF;
|
||||
return (int) MCAMath.getValueFromLongStream(this.worldSurfaceHeights, z * 16 + x, 9);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOceanFloorY(int x, int z) {
|
||||
if (this.oceanFloorHeights.length < 36) return 0;
|
||||
|
||||
x &= 0xF; z &= 0xF;
|
||||
return (int) MCAMath.getValueFromLongStream(this.oceanFloorHeights, z * 16 + x, 9);
|
||||
}
|
||||
|
||||
private static class Section {
|
||||
private static final String AIR_ID = "minecraft:air";
|
||||
|
||||
private int sectionY;
|
||||
private byte[] blockLight;
|
||||
private byte[] skyLight;
|
||||
private long[] blocks;
|
||||
private BlockState[] palette;
|
||||
|
||||
private int bitsPerBlock;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public Section(CompoundTag sectionData) {
|
||||
this.sectionY = sectionData.get("Y", NumberTag.class).asInt();
|
||||
this.blockLight = sectionData.getByteArray("BlockLight");
|
||||
this.skyLight = sectionData.getByteArray("SkyLight");
|
||||
this.blocks = sectionData.getLongArray("BlockStates");
|
||||
|
||||
if (blocks.length < 256 && blocks.length > 0) blocks = Arrays.copyOf(blocks, 256);
|
||||
if (blockLight.length < 2048 && blockLight.length > 0) blockLight = Arrays.copyOf(blockLight, 2048);
|
||||
if (skyLight.length < 2048 && skyLight.length > 0) skyLight = Arrays.copyOf(skyLight, 2048);
|
||||
|
||||
//read block palette
|
||||
ListTag<CompoundTag> paletteTag = (ListTag<CompoundTag>) sectionData.getListTag("Palette");
|
||||
if (paletteTag != null) {
|
||||
this.palette = new BlockState[paletteTag.size()];
|
||||
for (int i = 0; i < this.palette.length; i++) {
|
||||
CompoundTag stateTag = paletteTag.get(i);
|
||||
|
||||
String id = stateTag.getString("Name"); //shortcut to save time and memory
|
||||
if (id.equals(AIR_ID)) {
|
||||
palette[i] = BlockState.AIR;
|
||||
continue;
|
||||
}
|
||||
|
||||
Map<String, String> properties = new HashMap<>();
|
||||
|
||||
if (stateTag.containsKey("Properties")) {
|
||||
CompoundTag propertiesTag = stateTag.getCompoundTag("Properties");
|
||||
for (Entry<String, Tag<?>> property : propertiesTag) {
|
||||
properties.put(property.getKey().toLowerCase(), ((StringTag) property.getValue()).getValue().toLowerCase());
|
||||
}
|
||||
}
|
||||
|
||||
palette[i] = new BlockState(id, properties);
|
||||
}
|
||||
} else {
|
||||
this.palette = new BlockState[0];
|
||||
}
|
||||
|
||||
this.bitsPerBlock = this.blocks.length >> 6; // available longs * 64 (bits per long) / 4096 (blocks per section) (floored result)
|
||||
}
|
||||
|
||||
public int getSectionY() {
|
||||
return sectionY;
|
||||
}
|
||||
|
||||
public BlockState getBlockState(int x, int y, int z) {
|
||||
if (palette.length == 1) return palette[0];
|
||||
if (blocks.length == 0) return BlockState.AIR;
|
||||
|
||||
x &= 0xF; y &= 0xF; z &= 0xF; // Math.floorMod(pos.getX(), 16)
|
||||
|
||||
int blockIndex = y * 256 + z * 16 + x;
|
||||
|
||||
long value = MCAMath.getValueFromLongStream(blocks, blockIndex, bitsPerBlock);
|
||||
if (value >= palette.length) {
|
||||
Logger.global.noFloodWarning("palettewarning", "Got palette value " + value + " but palette has size of " + palette.length + " (Future occasions of this error will not be logged)");
|
||||
return BlockState.MISSING;
|
||||
}
|
||||
|
||||
return palette[(int) value];
|
||||
}
|
||||
|
||||
public LightData getLightData(int x, int y, int z, LightData target) {
|
||||
if (blockLight.length == 0 && skyLight.length == 0) return target.set(0, 0);
|
||||
|
||||
x &= 0xF; y &= 0xF; z &= 0xF; // Math.floorMod(pos.getX(), 16)
|
||||
|
||||
int blockByteIndex = y * 256 + z * 16 + x;
|
||||
int blockHalfByteIndex = blockByteIndex >> 1; // blockByteIndex / 2
|
||||
boolean largeHalf = (blockByteIndex & 0x1) != 0; // (blockByteIndex % 2) == 0
|
||||
|
||||
return target.set(
|
||||
this.skyLight.length > 0 ? MCAMath.getByteHalf(this.skyLight[blockHalfByteIndex], largeHalf) : 0,
|
||||
this.blockLight.length > 0 ? MCAMath.getByteHalf(this.blockLight[blockHalfByteIndex], largeHalf) : 0
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
}
|
|
@ -1,239 +0,0 @@
|
|||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
package de.bluecolored.bluemap.core.mca;
|
||||
|
||||
import de.bluecolored.bluemap.core.logger.Logger;
|
||||
import de.bluecolored.bluemap.core.mca.data.ChunkData;
|
||||
import de.bluecolored.bluemap.core.mca.data.HeightmapsData;
|
||||
import de.bluecolored.bluemap.core.mca.data.SectionData;
|
||||
import de.bluecolored.bluemap.core.world.Biome;
|
||||
import de.bluecolored.bluemap.core.world.BlockState;
|
||||
import de.bluecolored.bluemap.core.world.LightData;
|
||||
|
||||
@SuppressWarnings("FieldMayBeFinal")
|
||||
public class ChunkAnvil116 extends MCAChunk {
|
||||
|
||||
private final boolean isGenerated;
|
||||
private final boolean hasLight;
|
||||
|
||||
private final long inhabitedTime;
|
||||
|
||||
private final int sectionMin, sectionMax;
|
||||
private final Section[] sections;
|
||||
|
||||
private final int[] biomes;
|
||||
|
||||
private final long[] oceanFloorHeights;
|
||||
private final long[] worldSurfaceHeights;
|
||||
|
||||
public ChunkAnvil116(MCAWorld world, ChunkData chunkData) {
|
||||
super(world, chunkData);
|
||||
|
||||
String status = chunkData.getStatus();
|
||||
boolean generated = status.equals("full");
|
||||
this.hasLight = generated;
|
||||
if (!generated && getWorld().isIgnoreMissingLightData())
|
||||
generated = !status.equals("empty");
|
||||
this.isGenerated = generated;
|
||||
|
||||
this.inhabitedTime = chunkData.getInhabitedTime();
|
||||
|
||||
HeightmapsData heightmapsData = chunkData.getHeightmaps();
|
||||
this.worldSurfaceHeights = heightmapsData.getWorldSurface();
|
||||
this.oceanFloorHeights = heightmapsData.getOceanFloor();
|
||||
|
||||
SectionData[] sectionDatas = chunkData.getSections();
|
||||
if (sectionDatas != null && sectionDatas.length > 0) {
|
||||
int min = Integer.MAX_VALUE;
|
||||
int max = Integer.MIN_VALUE;
|
||||
|
||||
// find section min/max y
|
||||
for (SectionData sectionData : sectionDatas) {
|
||||
int y = sectionData.getY();
|
||||
if (min > y) min = y;
|
||||
if (max < y) max = y;
|
||||
}
|
||||
|
||||
// load sections into ordered array
|
||||
this.sections = new Section[1 + max - min];
|
||||
for (SectionData sectionData : sectionDatas) {
|
||||
Section section = new Section(sectionData);
|
||||
int y = section.getSectionY();
|
||||
|
||||
if (min > y) min = y;
|
||||
if (max < y) max = y;
|
||||
|
||||
sections[section.sectionY - min] = section;
|
||||
}
|
||||
|
||||
this.sectionMin = min;
|
||||
this.sectionMax = max;
|
||||
} else {
|
||||
this.sections = new Section[0];
|
||||
this.sectionMin = 0;
|
||||
this.sectionMax = 0;
|
||||
}
|
||||
|
||||
this.biomes = chunkData.getBiomes();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isGenerated() {
|
||||
return isGenerated;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getInhabitedTime() {
|
||||
return inhabitedTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockState getBlockState(int x, int y, int z) {
|
||||
int sectionY = y >> 4;
|
||||
|
||||
Section section = getSection(sectionY);
|
||||
if (section == null) return BlockState.AIR;
|
||||
|
||||
return section.getBlockState(x, y, z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LightData getLightData(int x, int y, int z, LightData target) {
|
||||
if (!hasLight) return target.set(getWorld().getSkyLight(), 0);
|
||||
|
||||
int sectionY = y >> 4;
|
||||
|
||||
Section section = getSection(sectionY);
|
||||
if (section == null) return (sectionY < sectionMin) ? target.set(0, 0) : target.set(getWorld().getSkyLight(), 0);
|
||||
|
||||
return section.getLightData(x, y, z, target);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBiome(int x, int y, int z) {
|
||||
if (biomes.length < 16) return Biome.DEFAULT.getFormatted();
|
||||
|
||||
x = (x & 0xF) / 4; // Math.floorMod(pos.getX(), 16)
|
||||
z = (z & 0xF) / 4;
|
||||
y = y / 4;
|
||||
int biomeIntIndex = y * 16 + z * 4 + x; // TODO: fix this for 1.17+ worlds with negative y?
|
||||
|
||||
// shift y up/down if not in range
|
||||
if (biomeIntIndex >= biomes.length) biomeIntIndex -= (((biomeIntIndex - biomes.length) >> 4) + 1) * 16;
|
||||
if (biomeIntIndex < 0) biomeIntIndex -= (biomeIntIndex >> 4) * 16;
|
||||
|
||||
return LegacyBiomes.idFor(biomes[biomeIntIndex]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMinY(int x, int z) {
|
||||
return sectionMin * 16;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxY(int x, int z) {
|
||||
return sectionMax * 16 + 15;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getWorldSurfaceY(int x, int z) {
|
||||
if (this.worldSurfaceHeights.length < 37) return 0;
|
||||
|
||||
x &= 0xF; z &= 0xF;
|
||||
return (int) MCAMath.getValueFromLongArray(this.worldSurfaceHeights, z * 16 + x, 9);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOceanFloorY(int x, int z) {
|
||||
if (this.oceanFloorHeights.length < 37) return 0;
|
||||
|
||||
x &= 0xF; z &= 0xF;
|
||||
return (int) MCAMath.getValueFromLongArray(this.oceanFloorHeights, z * 16 + x, 9);
|
||||
}
|
||||
|
||||
private Section getSection(int y) {
|
||||
y -= sectionMin;
|
||||
if (y < 0 || y >= this.sections.length) return null;
|
||||
return this.sections[y];
|
||||
}
|
||||
|
||||
private static class Section {
|
||||
private final int sectionY;
|
||||
private final byte[] blockLight;
|
||||
private final byte[] skyLight;
|
||||
private final long[] blocks;
|
||||
private final BlockState[] palette;
|
||||
|
||||
private final int bitsPerBlock;
|
||||
|
||||
public Section(SectionData sectionData) {
|
||||
this.sectionY = sectionData.getY();
|
||||
this.blockLight = sectionData.getBlockLight();
|
||||
this.skyLight = sectionData.getSkyLight();
|
||||
|
||||
this.blocks = sectionData.getBlockStatesData();
|
||||
this.palette = sectionData.getPalette();
|
||||
|
||||
this.bitsPerBlock = this.blocks.length >> 6; // available longs * 64 (bits per long) / 4096 (blocks per section) (floored result)
|
||||
}
|
||||
|
||||
public int getSectionY() {
|
||||
return sectionY;
|
||||
}
|
||||
|
||||
public BlockState getBlockState(int x, int y, int z) {
|
||||
if (palette.length == 1) return palette[0];
|
||||
if (blocks.length == 0) return BlockState.AIR;
|
||||
|
||||
x &= 0xF; y &= 0xF; z &= 0xF; // Math.floorMod(pos.getX(), 16)
|
||||
|
||||
int blockIndex = y * 256 + z * 16 + x;
|
||||
|
||||
long value = MCAMath.getValueFromLongArray(blocks, blockIndex, bitsPerBlock);
|
||||
if (value >= palette.length) {
|
||||
Logger.global.noFloodWarning("palettewarning", "Got palette value " + value + " but palette has size of " + palette.length + "! (Future occasions of this error will not be logged)");
|
||||
return BlockState.MISSING;
|
||||
}
|
||||
|
||||
return palette[(int) value];
|
||||
}
|
||||
|
||||
public LightData getLightData(int x, int y, int z, LightData target) {
|
||||
if (blockLight.length == 0 && skyLight.length == 0) return target.set(0, 0);
|
||||
|
||||
x &= 0xF; y &= 0xF; z &= 0xF; // Math.floorMod(pos.getX(), 16)
|
||||
|
||||
int blockByteIndex = y * 256 + z * 16 + x;
|
||||
int blockHalfByteIndex = blockByteIndex >> 1; // blockByteIndex / 2
|
||||
boolean largeHalf = (blockByteIndex & 0x1) != 0; // (blockByteIndex % 2) == 0
|
||||
|
||||
return target.set(
|
||||
this.skyLight.length > 0 ? MCAMath.getByteHalf(this.skyLight[blockHalfByteIndex], largeHalf) : 0,
|
||||
this.blockLight.length > 0 ? MCAMath.getByteHalf(this.blockLight[blockHalfByteIndex], largeHalf) : 0
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,247 +0,0 @@
|
|||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
package de.bluecolored.bluemap.core.mca;
|
||||
|
||||
import de.bluecolored.bluemap.core.logger.Logger;
|
||||
import de.bluecolored.bluemap.core.mca.data.*;
|
||||
import de.bluecolored.bluemap.core.world.Biome;
|
||||
import de.bluecolored.bluemap.core.world.BlockState;
|
||||
import de.bluecolored.bluemap.core.world.LightData;
|
||||
|
||||
public class ChunkAnvil118 extends MCAChunk {
|
||||
|
||||
private final boolean isGenerated;
|
||||
private final boolean hasLight;
|
||||
|
||||
private final long inhabitedTime;
|
||||
|
||||
private final int sectionMin, sectionMax;
|
||||
private final Section[] sections;
|
||||
|
||||
private final long[] oceanFloorHeights;
|
||||
private final long[] worldSurfaceHeights;
|
||||
|
||||
public ChunkAnvil118(MCAWorld world, ChunkData chunkData) {
|
||||
super(world, chunkData);
|
||||
|
||||
String status = chunkData.getStatus();
|
||||
boolean generated = status.equals("minecraft:full") || status.equals("full");
|
||||
this.hasLight = generated;
|
||||
if (!generated && getWorld().isIgnoreMissingLightData())
|
||||
generated = !status.equals("empty") && !status.equals("minecraft:empty");
|
||||
this.isGenerated = generated;
|
||||
|
||||
this.inhabitedTime = chunkData.getInhabitedTime();
|
||||
|
||||
HeightmapsData heightmapsData = chunkData.getHeightmaps();
|
||||
this.worldSurfaceHeights = heightmapsData.getWorldSurface();
|
||||
this.oceanFloorHeights = heightmapsData.getOceanFloor();
|
||||
|
||||
SectionData[] sectionDatas = chunkData.getSections();
|
||||
if (sectionDatas != null && sectionDatas.length > 0) {
|
||||
int min = Integer.MAX_VALUE;
|
||||
int max = Integer.MIN_VALUE;
|
||||
|
||||
// find section min/max y
|
||||
for (SectionData sectionData : sectionDatas) {
|
||||
int y = sectionData.getY();
|
||||
if (min > y) min = y;
|
||||
if (max < y) max = y;
|
||||
}
|
||||
|
||||
// load sections into ordered array
|
||||
this.sections = new Section[1 + max - min];
|
||||
for (SectionData sectionData : sectionDatas) {
|
||||
Section section = new Section(sectionData);
|
||||
int y = section.getSectionY();
|
||||
|
||||
if (min > y) min = y;
|
||||
if (max < y) max = y;
|
||||
|
||||
sections[section.sectionY - min] = section;
|
||||
}
|
||||
|
||||
this.sectionMin = min;
|
||||
this.sectionMax = max;
|
||||
} else {
|
||||
this.sections = new Section[0];
|
||||
this.sectionMin = 0;
|
||||
this.sectionMax = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isGenerated() {
|
||||
return isGenerated;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getInhabitedTime() {
|
||||
return inhabitedTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockState getBlockState(int x, int y, int z) {
|
||||
int sectionY = y >> 4;
|
||||
|
||||
Section section = getSection(sectionY);
|
||||
if (section == null) return BlockState.AIR;
|
||||
|
||||
return section.getBlockState(x, y, z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LightData getLightData(int x, int y, int z, LightData target) {
|
||||
if (!hasLight) return target.set(getWorld().getSkyLight(), 0);
|
||||
|
||||
int sectionY = y >> 4;
|
||||
|
||||
Section section = getSection(sectionY);
|
||||
if (section == null) return (sectionY < sectionMin) ? target.set(0, 0) : target.set(getWorld().getSkyLight(), 0);
|
||||
|
||||
return section.getLightData(x, y, z, target);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBiome(int x, int y, int z) {
|
||||
int sectionY = y >> 4;
|
||||
|
||||
Section section = getSection(sectionY);
|
||||
if (section == null) return Biome.DEFAULT.getFormatted();
|
||||
|
||||
return section.getBiome(x, y, z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMinY(int x, int z) {
|
||||
return sectionMin * 16;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxY(int x, int z) {
|
||||
return sectionMax * 16 + 15;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getWorldSurfaceY(int x, int z) {
|
||||
if (this.worldSurfaceHeights.length < 37) return 0;
|
||||
|
||||
x &= 0xF; z &= 0xF;
|
||||
return (int) MCAMath.getValueFromLongArray(this.worldSurfaceHeights, z * 16 + x, 9) - 64;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOceanFloorY(int x, int z) {
|
||||
if (this.oceanFloorHeights.length < 37) return 0;
|
||||
|
||||
x &= 0xF; z &= 0xF;
|
||||
return (int) MCAMath.getValueFromLongArray(this.oceanFloorHeights, z * 16 + x, 9) - 64;
|
||||
}
|
||||
|
||||
private Section getSection(int y) {
|
||||
y -= sectionMin;
|
||||
if (y < 0 || y >= this.sections.length) return null;
|
||||
return this.sections[y];
|
||||
}
|
||||
|
||||
private static class Section {
|
||||
private final int sectionY;
|
||||
private final byte[] blockLight;
|
||||
private final byte[] skyLight;
|
||||
private final long[] blocks;
|
||||
private final long[] biomes;
|
||||
private final BlockState[] blockPalette;
|
||||
private final String[] biomePalette;
|
||||
|
||||
private final int bitsPerBlock, bitsPerBiome;
|
||||
|
||||
public Section(SectionData sectionData) {
|
||||
this.sectionY = sectionData.getY();
|
||||
this.blockLight = sectionData.getBlockLight();
|
||||
this.skyLight = sectionData.getSkyLight();
|
||||
|
||||
BlockStatesData blockStates = sectionData.getBlockStates();
|
||||
this.blocks = blockStates.getData();
|
||||
this.blockPalette = blockStates.getPalette();
|
||||
|
||||
BiomesData biomesData = sectionData.getBiomes();
|
||||
this.biomes = biomesData.getData();
|
||||
this.biomePalette = biomesData.getPalette();
|
||||
|
||||
this.bitsPerBlock = this.blocks.length >> 6; // available longs * 64 (bits per long) / 4096 (blocks per section) (floored result)
|
||||
this.bitsPerBiome = MCAMath.ceilLog2(this.biomePalette.length);
|
||||
}
|
||||
|
||||
public int getSectionY() {
|
||||
return sectionY;
|
||||
}
|
||||
|
||||
public BlockState getBlockState(int x, int y, int z) {
|
||||
if (blockPalette.length == 1) return blockPalette[0];
|
||||
if (blocks.length == 0) return BlockState.AIR;
|
||||
|
||||
int blockIndex = (y & 0xF) << 8 | (z & 0xF) << 4 | x & 0xF;
|
||||
long value = MCAMath.getValueFromLongArray(blocks, blockIndex, bitsPerBlock);
|
||||
if (value >= blockPalette.length) {
|
||||
Logger.global.noFloodWarning("palettewarning", "Got block-palette value " + value + " but palette has size of " + blockPalette.length + "! (Future occasions of this error will not be logged)");
|
||||
return BlockState.MISSING;
|
||||
}
|
||||
|
||||
return blockPalette[(int) value];
|
||||
}
|
||||
|
||||
public LightData getLightData(int x, int y, int z, LightData target) {
|
||||
if (blockLight.length == 0 && skyLight.length == 0) return target.set(0, 0);
|
||||
|
||||
int blockByteIndex = (y & 0xF) << 8 | (z & 0xF) << 4 | x & 0xF;
|
||||
int blockHalfByteIndex = blockByteIndex >> 1; // blockByteIndex / 2
|
||||
boolean largeHalf = (blockByteIndex & 0x1) != 0; // (blockByteIndex % 2) == 0
|
||||
|
||||
return target.set(
|
||||
this.skyLight.length > 0 ? MCAMath.getByteHalf(this.skyLight[blockHalfByteIndex], largeHalf) : 0,
|
||||
this.blockLight.length > 0 ? MCAMath.getByteHalf(this.blockLight[blockHalfByteIndex], largeHalf) : 0
|
||||
);
|
||||
}
|
||||
|
||||
public String getBiome(int x, int y, int z) {
|
||||
if (biomePalette.length == 0) return Biome.DEFAULT.getValue();
|
||||
if (biomePalette.length == 1 || biomes.length == 0) return biomePalette[0];
|
||||
|
||||
int biomeIndex = (y & 0b1100) << 2 | z & 0b1100 | (x & 0b1100) >> 2;
|
||||
long value = MCAMath.getValueFromLongArray(biomes, biomeIndex, bitsPerBiome);
|
||||
if (value >= biomePalette.length) {
|
||||
Logger.global.noFloodWarning("biomepalettewarning", "Got biome-palette value " + value + " but palette has size of " + biomePalette.length + "! (Future occasions of this error will not be logged)");
|
||||
return Biome.DEFAULT.getValue();
|
||||
}
|
||||
|
||||
return biomePalette[(int) value];
|
||||
}
|
||||
}
|
||||
|
||||
private static PackedIntArrayAccess heightmap(int worldHeight, long[] data) {
|
||||
return new PackedIntArrayAccess(MCAMath.ceilLog2(worldHeight + 1), data);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,116 +0,0 @@
|
|||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
package de.bluecolored.bluemap.core.mca;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public class LegacyBiomes {
|
||||
|
||||
private static final String[] BIOME_IDS = new String[170];
|
||||
static {
|
||||
Arrays.fill(BIOME_IDS, "minecraft:ocean");
|
||||
BIOME_IDS[0] = "minecraft:ocean";
|
||||
BIOME_IDS[1] = "minecraft:plains";
|
||||
BIOME_IDS[2] = "minecraft:desert";
|
||||
BIOME_IDS[3] = "minecraft:mountains";
|
||||
BIOME_IDS[4] = "minecraft:forest";
|
||||
BIOME_IDS[5] = "minecraft:taiga";
|
||||
BIOME_IDS[6] = "minecraft:swamp";
|
||||
BIOME_IDS[7] = "minecraft:river";
|
||||
BIOME_IDS[8] = "minecraft:nether";
|
||||
BIOME_IDS[9] = "minecraft:the_end";
|
||||
BIOME_IDS[10] = "minecraft:frozen_ocean";
|
||||
BIOME_IDS[11] = "minecraft:frozen_river";
|
||||
BIOME_IDS[12] = "minecraft:snowy_tundra";
|
||||
BIOME_IDS[13] = "minecraft:snowy_mountains";
|
||||
BIOME_IDS[14] = "minecraft:mushroom_fields";
|
||||
BIOME_IDS[15] = "minecraft:mushroom_field_shore";
|
||||
BIOME_IDS[16] = "minecraft:beach";
|
||||
BIOME_IDS[17] = "minecraft:desert_hills";
|
||||
BIOME_IDS[18] = "minecraft:wooded_hills";
|
||||
BIOME_IDS[19] = "minecraft:taiga_hills";
|
||||
BIOME_IDS[20] = "minecraft:mountain_edge";
|
||||
BIOME_IDS[21] = "minecraft:jungle";
|
||||
BIOME_IDS[22] = "minecraft:jungle_hills";
|
||||
BIOME_IDS[23] = "minecraft:jungle_edge";
|
||||
BIOME_IDS[24] = "minecraft:deep_ocean";
|
||||
BIOME_IDS[25] = "minecraft:stone_shore";
|
||||
BIOME_IDS[26] = "minecraft:snowy_beach";
|
||||
BIOME_IDS[27] = "minecraft:birch_forest";
|
||||
BIOME_IDS[28] = "minecraft:birch_forest_hills";
|
||||
BIOME_IDS[29] = "minecraft:dark_forest";
|
||||
BIOME_IDS[30] = "minecraft:snowy_taiga";
|
||||
BIOME_IDS[31] = "minecraft:snowy_taiga_hills";
|
||||
BIOME_IDS[32] = "minecraft:giant_tree_taiga";
|
||||
BIOME_IDS[33] = "minecraft:giant_tree_taiga_hills";
|
||||
BIOME_IDS[34] = "minecraft:wooded_mountains";
|
||||
BIOME_IDS[35] = "minecraft:savanna";
|
||||
BIOME_IDS[36] = "minecraft:savanna_plateau";
|
||||
BIOME_IDS[37] = "minecraft:badlands";
|
||||
BIOME_IDS[38] = "minecraft:wooded_badlands_plateau";
|
||||
BIOME_IDS[39] = "minecraft:badlands_plateau";
|
||||
BIOME_IDS[40] = "minecraft:small_end_islands";
|
||||
BIOME_IDS[41] = "minecraft:end_midlands";
|
||||
BIOME_IDS[42] = "minecraft:end_highlands";
|
||||
BIOME_IDS[43] = "minecraft:end_barrens";
|
||||
BIOME_IDS[44] = "minecraft:warm_ocean";
|
||||
BIOME_IDS[45] = "minecraft:lukewarm_ocean";
|
||||
BIOME_IDS[46] = "minecraft:cold_ocean";
|
||||
BIOME_IDS[47] = "minecraft:deep_warm_ocean";
|
||||
BIOME_IDS[48] = "minecraft:deep_lukewarm_ocean";
|
||||
BIOME_IDS[49] = "minecraft:deep_cold_ocean";
|
||||
BIOME_IDS[50] = "minecraft:deep_frozen_ocean";
|
||||
BIOME_IDS[127] = "minecraft:the_void";
|
||||
BIOME_IDS[129] = "minecraft:sunflower_plains";
|
||||
BIOME_IDS[130] = "minecraft:desert_lakes";
|
||||
BIOME_IDS[131] = "minecraft:gravelly_mountains";
|
||||
BIOME_IDS[132] = "minecraft:flower_forest";
|
||||
BIOME_IDS[133] = "minecraft:taiga_mountains";
|
||||
BIOME_IDS[134] = "minecraft:swamp_hills";
|
||||
BIOME_IDS[140] = "minecraft:ice_spikes";
|
||||
BIOME_IDS[149] = "minecraft:modified_jungle";
|
||||
BIOME_IDS[151] = "minecraft:modified_jungle_edge";
|
||||
BIOME_IDS[155] = "minecraft:tall_birch_forest";
|
||||
BIOME_IDS[156] = "minecraft:tall_birch_hills";
|
||||
BIOME_IDS[157] = "minecraft:dark_forest_hills";
|
||||
BIOME_IDS[158] = "minecraft:snowy_taiga_mountains";
|
||||
BIOME_IDS[160] = "minecraft:giant_spruce_taiga";
|
||||
BIOME_IDS[161] = "minecraft:giant_spruce_taiga_hills";
|
||||
BIOME_IDS[162] = "minecraft:modified_gravelly_mountains";
|
||||
BIOME_IDS[163] = "minecraft:shattered_savanna";
|
||||
BIOME_IDS[164] = "minecraft:shattered_savanna_plateau";
|
||||
BIOME_IDS[165] = "minecraft:eroded_badlands";
|
||||
BIOME_IDS[166] = "minecraft:modified_wooded_badlands_plateau";
|
||||
BIOME_IDS[167] = "minecraft:modified_badlands_plateau";
|
||||
BIOME_IDS[168] = "minecraft:bamboo_jungle";
|
||||
BIOME_IDS[169] = "minecraft:bamboo_jungle_hills";
|
||||
}
|
||||
|
||||
public static String idFor(int legacyId) {
|
||||
if (legacyId < 0 || legacyId >= BIOME_IDS.length) legacyId = 0;
|
||||
return BIOME_IDS[legacyId];
|
||||
}
|
||||
|
||||
}
|
|
@ -1,114 +0,0 @@
|
|||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
package de.bluecolored.bluemap.core.mca;
|
||||
|
||||
import de.bluecolored.bluemap.core.mca.data.ChunkData;
|
||||
import de.bluecolored.bluemap.core.world.BlockState;
|
||||
import de.bluecolored.bluemap.core.world.Chunk;
|
||||
import de.bluecolored.bluemap.core.world.LightData;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public abstract class MCAChunk implements Chunk {
|
||||
|
||||
private final MCAWorld world;
|
||||
private final int dataVersion;
|
||||
|
||||
protected MCAChunk() {
|
||||
this.world = null;
|
||||
this.dataVersion = -1;
|
||||
}
|
||||
|
||||
protected MCAChunk(MCAWorld world) {
|
||||
this.world = world;
|
||||
this.dataVersion = -1;
|
||||
}
|
||||
|
||||
protected MCAChunk(MCAWorld world, ChunkData chunkData) {
|
||||
this.world = world;
|
||||
dataVersion = chunkData.getDataVersion();
|
||||
}
|
||||
|
||||
@Override
|
||||
public abstract boolean isGenerated();
|
||||
|
||||
public int getDataVersion() {
|
||||
return dataVersion;
|
||||
}
|
||||
|
||||
@Override
|
||||
public abstract long getInhabitedTime();
|
||||
|
||||
@Override
|
||||
public abstract BlockState getBlockState(int x, int y, int z);
|
||||
|
||||
@Override
|
||||
public abstract LightData getLightData(int x, int y, int z, LightData target);
|
||||
|
||||
@Override
|
||||
public abstract String getBiome(int x, int y, int z);
|
||||
|
||||
@Override
|
||||
public int getMaxY(int x, int z) {
|
||||
return 255;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMinY(int x, int z) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getWorldSurfaceY(int x, int z) { return 0; }
|
||||
|
||||
@Override
|
||||
public int getOceanFloorY(int x, int z) { return 0; }
|
||||
|
||||
protected MCAWorld getWorld() {
|
||||
return world;
|
||||
}
|
||||
|
||||
public static MCAChunk create(MCAWorld world, ChunkData chunkData) throws IOException {
|
||||
int version = chunkData.getDataVersion();
|
||||
|
||||
/*
|
||||
if (version < 2200) return new ChunkAnvil113(world, chunkData);
|
||||
if (version < 2500) return new ChunkAnvil115(world, chunkData);
|
||||
*/
|
||||
if (version < 2844) return new ChunkAnvil116(world, chunkData);
|
||||
|
||||
return new ChunkAnvil118(world, chunkData);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "MCAChunk{" +
|
||||
"world=" + world +
|
||||
"dataVersion=" + dataVersion +
|
||||
"isGenerated()=" + isGenerated() +
|
||||
'}';
|
||||
}
|
||||
|
||||
}
|
|
@ -1,285 +0,0 @@
|
|||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
package de.bluecolored.bluemap.core.mca;
|
||||
|
||||
import com.flowpowered.math.vector.Vector2i;
|
||||
import com.flowpowered.math.vector.Vector3i;
|
||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||
import com.github.benmanes.caffeine.cache.LoadingCache;
|
||||
import de.bluecolored.bluemap.api.debug.DebugDump;
|
||||
import de.bluecolored.bluemap.core.BlueMap;
|
||||
import de.bluecolored.bluemap.core.logger.Logger;
|
||||
import de.bluecolored.bluemap.core.mca.data.LevelData;
|
||||
import de.bluecolored.bluemap.core.mca.region.RegionType;
|
||||
import de.bluecolored.bluemap.core.util.Vector2iCache;
|
||||
import de.bluecolored.bluemap.core.world.*;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
@DebugDump
|
||||
public class MCAWorld implements World {
|
||||
|
||||
private static final Grid CHUNK_GRID = new Grid(16);
|
||||
private static final Grid REGION_GRID = new Grid(32).multiply(CHUNK_GRID);
|
||||
|
||||
private static final Vector2iCache VECTOR_2_I_CACHE = new Vector2iCache();
|
||||
|
||||
private final Path worldFolder;
|
||||
|
||||
private final String name;
|
||||
private final Vector3i spawnPoint;
|
||||
|
||||
private final int skyLight;
|
||||
private final boolean ignoreMissingLightData;
|
||||
|
||||
private final LoadingCache<Vector2i, Region> regionCache;
|
||||
private final LoadingCache<Vector2i, Chunk> chunkCache;
|
||||
|
||||
public MCAWorld(Path worldFolder, int skyLight, boolean ignoreMissingLightData) throws IOException {
|
||||
this.worldFolder = worldFolder.toRealPath();
|
||||
this.skyLight = skyLight;
|
||||
this.ignoreMissingLightData = ignoreMissingLightData;
|
||||
|
||||
this.regionCache = Caffeine.newBuilder()
|
||||
.executor(BlueMap.THREAD_POOL)
|
||||
.maximumSize(100)
|
||||
.expireAfterWrite(1, TimeUnit.MINUTES)
|
||||
.build(this::loadRegion);
|
||||
|
||||
this.chunkCache = Caffeine.newBuilder()
|
||||
.executor(BlueMap.THREAD_POOL)
|
||||
.maximumSize(500)
|
||||
.expireAfterWrite(1, TimeUnit.MINUTES)
|
||||
.build(this::loadChunk);
|
||||
|
||||
Path levelFile = resolveLevelFile(worldFolder);
|
||||
try (InputStream in = new GZIPInputStream(new BufferedInputStream(Files.newInputStream(levelFile)))) {
|
||||
LevelData level = MCAMath.BLUENBT.read(in, LevelData.class);
|
||||
LevelData.Data levelData = level.getData();
|
||||
this.name = levelData.getLevelName();
|
||||
this.spawnPoint = new Vector3i(
|
||||
levelData.getSpawnX(),
|
||||
levelData.getSpawnY(),
|
||||
levelData.getSpawnZ()
|
||||
);
|
||||
} catch (IOException ex) {
|
||||
throw new IOException("Failed to read level.dat!", ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Chunk getChunkAtBlock(int x, int y, int z) {
|
||||
return getChunk(x >> 4, z >> 4);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Chunk getChunk(int x, int z) {
|
||||
return getChunk(VECTOR_2_I_CACHE.get(x, z));
|
||||
}
|
||||
|
||||
private Chunk getChunk(Vector2i pos) {
|
||||
return chunkCache.get(pos);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Region getRegion(int x, int z) {
|
||||
return getRegion(VECTOR_2_I_CACHE.get(x, z));
|
||||
}
|
||||
|
||||
private Region getRegion(Vector2i pos) {
|
||||
return regionCache.get(pos);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Vector2i> listRegions() {
|
||||
File[] regionFiles = getRegionFolder().toFile().listFiles();
|
||||
if (regionFiles == null) return Collections.emptyList();
|
||||
|
||||
List<Vector2i> regions = new ArrayList<>(regionFiles.length);
|
||||
|
||||
for (File file : regionFiles) {
|
||||
if (RegionType.forFileName(file.getName()) == null) continue;
|
||||
if (file.length() <= 0) continue;
|
||||
|
||||
try {
|
||||
String[] filenameParts = file.getName().split("\\.");
|
||||
int rX = Integer.parseInt(filenameParts[1]);
|
||||
int rZ = Integer.parseInt(filenameParts[2]);
|
||||
|
||||
regions.add(new Vector2i(rX, rZ));
|
||||
} catch (NumberFormatException ignore) {}
|
||||
}
|
||||
|
||||
return regions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Path getSaveFolder() {
|
||||
return worldFolder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSkyLight() {
|
||||
return skyLight;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMinY(int x, int z) {
|
||||
return getChunk(x >> 4, z >> 4).getMinY(x, z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxY(int x, int z) {
|
||||
return getChunk(x >> 4, z >> 4).getMaxY(x, z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Grid getChunkGrid() {
|
||||
return CHUNK_GRID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Grid getRegionGrid() {
|
||||
return REGION_GRID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Vector3i getSpawnPoint() {
|
||||
return spawnPoint;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateChunkCache() {
|
||||
chunkCache.invalidateAll();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateChunkCache(int x, int z) {
|
||||
chunkCache.invalidate(VECTOR_2_I_CACHE.get(x, z));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cleanUpChunkCache() {
|
||||
chunkCache.cleanUp();
|
||||
}
|
||||
|
||||
public Path getWorldFolder() {
|
||||
return worldFolder;
|
||||
}
|
||||
|
||||
private Path getRegionFolder() {
|
||||
return worldFolder.resolve("region");
|
||||
}
|
||||
|
||||
public boolean isIgnoreMissingLightData() {
|
||||
return ignoreMissingLightData;
|
||||
}
|
||||
|
||||
private Region loadRegion(Vector2i regionPos) {
|
||||
return loadRegion(regionPos.getX(), regionPos.getY());
|
||||
}
|
||||
|
||||
Region loadRegion(int x, int z) {
|
||||
return RegionType.loadRegion(this, getRegionFolder(), x, z);
|
||||
}
|
||||
|
||||
private Chunk loadChunk(Vector2i chunkPos) {
|
||||
return loadChunk(chunkPos.getX(), chunkPos.getY());
|
||||
}
|
||||
|
||||
Chunk loadChunk(int x, int z) {
|
||||
final int tries = 3;
|
||||
final int tryInterval = 1000;
|
||||
|
||||
Exception loadException = null;
|
||||
for (int i = 0; i < tries; i++) {
|
||||
try {
|
||||
return getRegion(x >> 5, z >> 5)
|
||||
.loadChunk(x, z, ignoreMissingLightData);
|
||||
} catch (IOException | RuntimeException e) {
|
||||
if (loadException != null) e.addSuppressed(loadException);
|
||||
loadException = e;
|
||||
|
||||
if (i + 1 < tries) {
|
||||
try {
|
||||
Thread.sleep(tryInterval);
|
||||
} catch (InterruptedException ex) {
|
||||
Thread.currentThread().interrupt();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Logger.global.logDebug("Unexpected exception trying to load chunk (x:" + x + ", z:" + z + "):" + loadException);
|
||||
return EmptyChunk.INSTANCE;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "MCAWorld{" +
|
||||
"worldFolder=" + worldFolder +
|
||||
", name='" + name + '\'' +
|
||||
", spawnPoint=" + spawnPoint +
|
||||
", skyLight=" + skyLight +
|
||||
", ignoreMissingLightData=" + ignoreMissingLightData +
|
||||
'}';
|
||||
}
|
||||
|
||||
private static Path resolveLevelFile(Path worldFolder) throws IOException {
|
||||
Path levelFolder = worldFolder.toRealPath();
|
||||
Path levelFile = levelFolder.resolve("level.dat");
|
||||
int searchDepth = 0;
|
||||
|
||||
while (!Files.isRegularFile(levelFile) && searchDepth < 4) {
|
||||
searchDepth++;
|
||||
levelFolder = levelFolder.getParent();
|
||||
if (levelFolder == null) break;
|
||||
|
||||
levelFile = levelFolder.resolve("level.dat");
|
||||
}
|
||||
|
||||
if (!Files.isRegularFile(levelFile))
|
||||
throw new FileNotFoundException("Could not find a level.dat file for this world!");
|
||||
|
||||
return levelFile;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
|
||||
}
|
|
@ -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;
|
||||
|
||||
}
|
|
@ -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;
|
||||
|
||||
}
|
|
@ -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;
|
||||
|
||||
}
|
|
@ -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;
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
|
||||
}
|
|
@ -1,209 +0,0 @@
|
|||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
package de.bluecolored.bluemap.core.mca.region;
|
||||
|
||||
import com.flowpowered.math.vector.Vector2i;
|
||||
import de.bluecolored.bluemap.core.logger.Logger;
|
||||
import de.bluecolored.bluemap.core.mca.MCAChunk;
|
||||
import de.bluecolored.bluemap.core.mca.MCAMath;
|
||||
import de.bluecolored.bluemap.core.mca.MCAWorld;
|
||||
import de.bluecolored.bluemap.core.mca.data.ChunkData;
|
||||
import de.bluecolored.bluemap.core.world.Chunk;
|
||||
import de.bluecolored.bluemap.core.world.EmptyChunk;
|
||||
import de.bluecolored.bluemap.core.world.Region;
|
||||
import io.airlift.compress.zstd.ZstdInputStream;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
|
||||
public class LinearRegion implements Region {
|
||||
|
||||
public static final String FILE_SUFFIX = ".linear";
|
||||
|
||||
private static final List<Byte> SUPPORTED_VERSIONS = Arrays.asList((byte) 1, (byte) 2);
|
||||
private static final long SUPERBLOCK = -4323716122432332390L;
|
||||
private static final int HEADER_SIZE = 32;
|
||||
private static final int FOOTER_SIZE = 8;
|
||||
|
||||
private final MCAWorld world;
|
||||
private final Path regionFile;
|
||||
private final Vector2i regionPos;
|
||||
|
||||
|
||||
public LinearRegion(MCAWorld world, Path regionFile) throws IllegalArgumentException {
|
||||
this.world = world;
|
||||
this.regionFile = regionFile;
|
||||
|
||||
String[] filenameParts = regionFile.getFileName().toString().split("\\.");
|
||||
int rX = Integer.parseInt(filenameParts[1]);
|
||||
int rZ = Integer.parseInt(filenameParts[2]);
|
||||
|
||||
this.regionPos = new Vector2i(rX, rZ);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Chunk loadChunk(int chunkX, int chunkZ, boolean ignoreMissingLightData) throws IOException {
|
||||
if (Files.notExists(regionFile)) return EmptyChunk.INSTANCE;
|
||||
|
||||
long fileLength = Files.size(regionFile);
|
||||
if (fileLength == 0) return EmptyChunk.INSTANCE;
|
||||
|
||||
try (InputStream inputStream = Files.newInputStream(regionFile);
|
||||
DataInputStream rawDataStream = new DataInputStream(inputStream)) {
|
||||
|
||||
long superBlock = rawDataStream.readLong();
|
||||
if (superBlock != SUPERBLOCK)
|
||||
throw new RuntimeException("Invalid superblock: " + superBlock + " file " + regionFile);
|
||||
|
||||
byte version = rawDataStream.readByte();
|
||||
if (!SUPPORTED_VERSIONS.contains(version))
|
||||
throw new RuntimeException("Invalid version: " + version + " file " + regionFile);
|
||||
|
||||
// Skip newestTimestamp (Long) + Compression level (Byte) + Chunk count (Short): Unused.
|
||||
rawDataStream.skipBytes(11);
|
||||
|
||||
int dataCount = rawDataStream.readInt();
|
||||
if (fileLength != HEADER_SIZE + dataCount + FOOTER_SIZE)
|
||||
throw new RuntimeException("Invalid file length: " + this.regionFile + " " + fileLength + " " + (HEADER_SIZE + dataCount + FOOTER_SIZE));
|
||||
|
||||
// Skip data hash (Long): Unused.
|
||||
rawDataStream.skipBytes(8);
|
||||
|
||||
byte[] rawCompressed = new byte[dataCount];
|
||||
rawDataStream.readFully(rawCompressed, 0, dataCount);
|
||||
|
||||
superBlock = rawDataStream.readLong();
|
||||
if (superBlock != SUPERBLOCK)
|
||||
throw new RuntimeException("Invalid footer superblock: " + this.regionFile);
|
||||
|
||||
try (DataInputStream dis = new DataInputStream(new ZstdInputStream(new ByteArrayInputStream(rawCompressed)))) {
|
||||
int x = chunkX - (regionPos.getX() << 5);
|
||||
int z = chunkZ - (regionPos.getY() << 5);
|
||||
int pos = (z << 5) + x;
|
||||
int skip = 0;
|
||||
|
||||
for (int i = 0; i < pos; i++) {
|
||||
skip += dis.readInt(); // Size of the chunk (bytes) to skip
|
||||
dis.skipBytes(4); // Skip timestamps
|
||||
}
|
||||
|
||||
int size = dis.readInt();
|
||||
if (size <= 0) return EmptyChunk.INSTANCE;
|
||||
|
||||
dis.skipBytes(((1024 - pos - 1) << 3) + 4); // Skip current chunk 0 and unneeded other chunks zero/size
|
||||
dis.skipBytes(skip); // Skip unneeded chunks data
|
||||
|
||||
ChunkData chunkData = MCAMath.BLUENBT.read(dis, ChunkData.class);
|
||||
return MCAChunk.create(world, chunkData);
|
||||
}
|
||||
} catch (RuntimeException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Vector2i> listChunks(long modifiedSince) {
|
||||
if (Files.notExists(regionFile)) return Collections.emptyList();
|
||||
|
||||
long fileLength;
|
||||
try {
|
||||
fileLength = Files.size(regionFile);
|
||||
if (fileLength == 0) return Collections.emptyList();
|
||||
} catch (IOException ex) {
|
||||
Logger.global.logWarning("Failed to read file-size for file: " + regionFile);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
List<Vector2i> chunks = new ArrayList<>(1024); //1024 = 32 x 32 chunks per region-file
|
||||
try (InputStream inputStream = Files.newInputStream(regionFile);
|
||||
DataInputStream rawDataStream = new DataInputStream(inputStream)) {
|
||||
|
||||
long superBlock = rawDataStream.readLong();
|
||||
if (superBlock != SUPERBLOCK)
|
||||
throw new RuntimeException("Invalid superblock: " + superBlock + " file " + regionFile);
|
||||
|
||||
byte version = rawDataStream.readByte();
|
||||
if (!SUPPORTED_VERSIONS.contains(version))
|
||||
throw new RuntimeException("Invalid version: " + version + " file " + regionFile);
|
||||
|
||||
int date = (int) (modifiedSince / 1000);
|
||||
|
||||
// If whole region is the same - skip.
|
||||
long newestTimestamp = rawDataStream.readLong();
|
||||
if (newestTimestamp < date) return Collections.emptyList();
|
||||
|
||||
// Linear v1 files store whole region timestamp, not chunk timestamp. We need to render the whole region file.
|
||||
if (version == 1) {
|
||||
for(int i = 0 ; i < 1024; i++)
|
||||
chunks.add(new Vector2i((regionPos.getX() << 5) + (i & 31), (regionPos.getY() << 5) + (i >> 5)));
|
||||
return chunks;
|
||||
}
|
||||
// Linear v2: Chunk timestamps are here!
|
||||
// Skip Compression level (Byte) + Chunk count (Short): Unused.
|
||||
rawDataStream.skipBytes(3);
|
||||
|
||||
int dataCount = rawDataStream.readInt();
|
||||
if (fileLength != HEADER_SIZE + dataCount + FOOTER_SIZE)
|
||||
throw new RuntimeException("Invalid file length: " + this.regionFile + " " + fileLength + " " + (HEADER_SIZE + dataCount + FOOTER_SIZE));
|
||||
|
||||
// Skip data hash (Long): Unused.
|
||||
rawDataStream.skipBytes(8);
|
||||
|
||||
byte[] rawCompressed = new byte[dataCount];
|
||||
rawDataStream.readFully(rawCompressed, 0, dataCount);
|
||||
|
||||
superBlock = rawDataStream.readLong();
|
||||
if (superBlock != SUPERBLOCK)
|
||||
throw new RuntimeException("Invalid footer SuperBlock: " + this.regionFile);
|
||||
|
||||
try (DataInputStream dis = new DataInputStream(new ZstdInputStream(new ByteArrayInputStream(rawCompressed)))) {
|
||||
for (int i = 0; i < 1024; i++) {
|
||||
dis.skipBytes(4); // Skip size of the chunk
|
||||
int timestamp = dis.readInt();
|
||||
if (timestamp >= date) // Timestamps
|
||||
chunks.add(new Vector2i((regionPos.getX() << 5) + (i & 31), (regionPos.getY() << 5) + (i >> 5)));
|
||||
}
|
||||
}
|
||||
} catch (RuntimeException | IOException ex) {
|
||||
Logger.global.logWarning("Failed to read .linear file: " + regionFile + " (" + ex + ")");
|
||||
}
|
||||
return chunks;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Path getRegionFile() {
|
||||
return regionFile;
|
||||
}
|
||||
|
||||
public static String getRegionFileName(int regionX, int regionZ) {
|
||||
return "r." + regionX + "." + regionZ + FILE_SUFFIX;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,162 +0,0 @@
|
|||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
package de.bluecolored.bluemap.core.mca.region;
|
||||
|
||||
import com.flowpowered.math.vector.Vector2i;
|
||||
import de.bluecolored.bluemap.core.logger.Logger;
|
||||
import de.bluecolored.bluemap.core.mca.MCAChunk;
|
||||
import de.bluecolored.bluemap.core.mca.MCAMath;
|
||||
import de.bluecolored.bluemap.core.mca.MCAWorld;
|
||||
import de.bluecolored.bluemap.core.mca.data.ChunkData;
|
||||
import de.bluecolored.bluemap.core.storage.Compression;
|
||||
import de.bluecolored.bluemap.core.world.Chunk;
|
||||
import de.bluecolored.bluemap.core.world.EmptyChunk;
|
||||
import de.bluecolored.bluemap.core.world.Region;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class MCARegion implements Region {
|
||||
|
||||
public static final String FILE_SUFFIX = ".mca";
|
||||
|
||||
private final MCAWorld world;
|
||||
private final Path regionFile;
|
||||
private final Vector2i regionPos;
|
||||
|
||||
public MCARegion(MCAWorld world, Path regionFile) throws IllegalArgumentException {
|
||||
this.world = world;
|
||||
this.regionFile = regionFile;
|
||||
|
||||
String[] filenameParts = regionFile.getFileName().toString().split("\\.");
|
||||
int rX = Integer.parseInt(filenameParts[1]);
|
||||
int rZ = Integer.parseInt(filenameParts[2]);
|
||||
|
||||
this.regionPos = new Vector2i(rX, rZ);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Chunk loadChunk(int chunkX, int chunkZ, boolean ignoreMissingLightData) throws IOException {
|
||||
if (Files.notExists(regionFile)) return EmptyChunk.INSTANCE;
|
||||
|
||||
long fileLength = Files.size(regionFile);
|
||||
if (fileLength == 0) return EmptyChunk.INSTANCE;
|
||||
|
||||
try (RandomAccessFile raf = new RandomAccessFile(regionFile.toFile(), "r")) {
|
||||
|
||||
int xzChunk = Math.floorMod(chunkZ, 32) * 32 + Math.floorMod(chunkX, 32);
|
||||
|
||||
raf.seek(xzChunk * 4L);
|
||||
int offset = raf.read() << 16;
|
||||
offset |= (raf.read() & 0xFF) << 8;
|
||||
offset |= raf.read() & 0xFF;
|
||||
offset *= 4096;
|
||||
|
||||
int size = raf.readByte() * 4096;
|
||||
if (size == 0) {
|
||||
return EmptyChunk.INSTANCE;
|
||||
}
|
||||
|
||||
raf.seek(offset + 4); // +4 skip chunk size
|
||||
|
||||
byte compressionByte = raf.readByte();
|
||||
Compression compression;
|
||||
switch (compressionByte) {
|
||||
case 0:
|
||||
case 3: compression = Compression.NONE; break;
|
||||
case 1: compression = Compression.GZIP; break;
|
||||
case 2: compression = Compression.DEFLATE; break;
|
||||
default: throw new IOException("Invalid compression type " + compressionByte);
|
||||
}
|
||||
|
||||
DataInputStream dis = new DataInputStream(new BufferedInputStream(compression.decompress(new FileInputStream(raf.getFD()))));
|
||||
ChunkData chunkData = MCAMath.BLUENBT.read(dis, ChunkData.class);
|
||||
return MCAChunk.create(world, chunkData);
|
||||
|
||||
} catch (RuntimeException e) {
|
||||
Logger.global.logError("Failed to load Chunk!", e);
|
||||
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Vector2i> listChunks(long modifiedSince) {
|
||||
if (Files.notExists(regionFile)) return Collections.emptyList();
|
||||
|
||||
try {
|
||||
long fileLength = Files.size(regionFile);
|
||||
if (fileLength == 0) return Collections.emptyList();
|
||||
} catch (IOException ex) {
|
||||
Logger.global.logWarning("Failed to read file-size for file: " + regionFile);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
List<Vector2i> chunks = new ArrayList<>(1024); //1024 = 32 x 32 chunks per region-file
|
||||
|
||||
try (RandomAccessFile raf = new RandomAccessFile(regionFile.toFile(), "r")) {
|
||||
for (int x = 0; x < 32; x++) {
|
||||
for (int z = 0; z < 32; z++) {
|
||||
Vector2i chunk = new Vector2i(regionPos.getX() * 32 + x, regionPos.getY() * 32 + z);
|
||||
int xzChunk = z * 32 + x;
|
||||
|
||||
raf.seek(xzChunk * 4 + 3);
|
||||
int size = raf.readByte() * 4096;
|
||||
|
||||
if (size == 0) continue;
|
||||
|
||||
raf.seek(xzChunk * 4 + 4096);
|
||||
int timestamp = raf.read() << 24;
|
||||
timestamp |= (raf.read() & 0xFF) << 16;
|
||||
timestamp |= (raf.read() & 0xFF) << 8;
|
||||
timestamp |= raf.read() & 0xFF;
|
||||
|
||||
if (timestamp >= (modifiedSince / 1000)) {
|
||||
chunks.add(chunk);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (RuntimeException | IOException ex) {
|
||||
Logger.global.logWarning("Failed to read .mca file: " + regionFile + " (" + ex + ")");
|
||||
}
|
||||
|
||||
return chunks;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Path getRegionFile() {
|
||||
return regionFile;
|
||||
}
|
||||
|
||||
public static String getRegionFileName(int regionX, int regionZ) {
|
||||
return "r." + regionX + "." + regionZ + FILE_SUFFIX;
|
||||
}
|
||||
|
||||
}
|
|
@ -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";
|
||||
|
||||
}
|
|
@ -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";
|
||||
|
||||
}
|
|
@ -29,7 +29,7 @@ import com.google.gson.stream.JsonReader;
|
|||
import de.bluecolored.bluemap.api.debug.DebugDump;
|
||||
import de.bluecolored.bluemap.core.util.math.Color;
|
||||
import de.bluecolored.bluemap.core.world.Biome;
|
||||
import de.bluecolored.bluemap.core.world.BlockNeighborhood;
|
||||
import de.bluecolored.bluemap.core.world.block.BlockNeighborhood;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.BufferedReader;
|
||||
|
|
|
@ -77,9 +77,6 @@ public class ResourcePath<T> extends Key {
|
|||
if (filePath.getNameCount() < 4)
|
||||
throw new IllegalArgumentException("The provided filePath has less than 4 segments!");
|
||||
|
||||
if (!filePath.getName(0).toString().equalsIgnoreCase("assets"))
|
||||
throw new IllegalArgumentException("The provided filePath doesn't start with 'assets'!");
|
||||
|
||||
String namespace = filePath.getName(1).toString();
|
||||
String path = filePath.subpath(3, filePath.getNameCount()).toString().replace(filePath.getFileSystem().getSeparator(), "/");
|
||||
|
||||
|
|
|
@ -25,22 +25,21 @@
|
|||
package de.bluecolored.bluemap.core.resources.adapter;
|
||||
|
||||
import com.flowpowered.math.vector.*;
|
||||
import com.google.gson.FieldNamingPolicy;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonToken;
|
||||
import de.bluecolored.bluemap.core.resources.resourcepack.blockmodel.Face;
|
||||
import de.bluecolored.bluemap.core.util.Direction;
|
||||
import de.bluecolored.bluemap.core.util.math.Axis;
|
||||
import de.bluecolored.bluemap.core.util.math.Color;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.EnumMap;
|
||||
|
||||
public class ResourcesGson {
|
||||
|
||||
public static final Gson INSTANCE = addAdapter(new GsonBuilder())
|
||||
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
|
||||
.setLenient()
|
||||
.create();
|
||||
|
||||
|
@ -60,9 +59,4 @@ public class ResourcesGson {
|
|||
);
|
||||
}
|
||||
|
||||
public static String nextStringOrBoolean(JsonReader in) throws IOException {
|
||||
if (in.peek() == JsonToken.BOOLEAN) return Boolean.toString(in.nextBoolean());
|
||||
return in.nextString();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -25,8 +25,10 @@
|
|||
package de.bluecolored.bluemap.core.resources.biome.datapack;
|
||||
|
||||
import de.bluecolored.bluemap.core.world.Biome;
|
||||
import lombok.Getter;
|
||||
|
||||
@SuppressWarnings("FieldMayBeFinal")
|
||||
@Getter
|
||||
public class DpBiome {
|
||||
|
||||
private DpBiomeEffects effects = new DpBiomeEffects();
|
||||
|
@ -44,16 +46,4 @@ public class DpBiome {
|
|||
);
|
||||
}
|
||||
|
||||
public DpBiomeEffects getEffects() {
|
||||
return effects;
|
||||
}
|
||||
|
||||
public double getTemperature() {
|
||||
return temperature;
|
||||
}
|
||||
|
||||
public double getDownfall() {
|
||||
return downfall;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -26,24 +26,14 @@ package de.bluecolored.bluemap.core.resources.biome.datapack;
|
|||
|
||||
import de.bluecolored.bluemap.core.util.math.Color;
|
||||
import de.bluecolored.bluemap.core.world.Biome;
|
||||
import lombok.Getter;
|
||||
|
||||
@SuppressWarnings("FieldMayBeFinal")
|
||||
@Getter
|
||||
public class DpBiomeEffects {
|
||||
|
||||
private Color water_color = Biome.DEFAULT.getWaterColor();
|
||||
private Color foliage_color = Biome.DEFAULT.getOverlayFoliageColor();
|
||||
private Color grass_color = Biome.DEFAULT.getOverlayGrassColor();
|
||||
|
||||
public Color getWaterColor() {
|
||||
return water_color;
|
||||
}
|
||||
|
||||
public Color getFoliageColor() {
|
||||
return foliage_color;
|
||||
}
|
||||
|
||||
public Color getGrassColor() {
|
||||
return grass_color;
|
||||
}
|
||||
private Color waterColor = Biome.DEFAULT.getWaterColor();
|
||||
private Color foliageColor = Biome.DEFAULT.getOverlayFoliageColor();
|
||||
private Color grassColor = Biome.DEFAULT.getOverlayGrassColor();
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,111 @@
|
|||
package de.bluecolored.bluemap.core.resources.datapack;
|
||||
|
||||
import de.bluecolored.bluemap.core.logger.Logger;
|
||||
import de.bluecolored.bluemap.core.resources.ResourcePath;
|
||||
import de.bluecolored.bluemap.core.resources.adapter.ResourcesGson;
|
||||
import de.bluecolored.bluemap.core.resources.datapack.dimension.DimensionTypeData;
|
||||
import de.bluecolored.bluemap.core.util.Key;
|
||||
import de.bluecolored.bluemap.core.world.DimensionType;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.FileSystem;
|
||||
import java.nio.file.FileSystems;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletionException;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class DataPack {
|
||||
|
||||
public static final Key DIMENSION_OVERWORLD = new Key("minecraft", "overworld");
|
||||
public static final Key DIMENSION_THE_NETHER = new Key("minecraft", "the_nether");
|
||||
public static final Key DIMENSION_THE_END = new Key("minecraft", "the_end");
|
||||
|
||||
private final Map<Key, DimensionType> dimensionTypes = new HashMap<>();
|
||||
|
||||
@Nullable
|
||||
public DimensionType getDimensionType(Key key) {
|
||||
return dimensionTypes.get(key);
|
||||
}
|
||||
|
||||
public void load(Path root) {
|
||||
Logger.global.logDebug("Loading datapack from: " + root + " ...");
|
||||
loadPath(root);
|
||||
}
|
||||
|
||||
private void loadPath(Path root) {
|
||||
if (!Files.isDirectory(root)) {
|
||||
try (FileSystem fileSystem = FileSystems.newFileSystem(root, (ClassLoader) null)) {
|
||||
for (Path fsRoot : fileSystem.getRootDirectories()) {
|
||||
if (!Files.isDirectory(fsRoot)) continue;
|
||||
loadPath(fsRoot);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
Logger.global.logDebug("Failed to read '" + root + "': " + ex);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
list(root.resolve("data"))
|
||||
.map(path -> path.resolve("dimension_type"))
|
||||
.filter(Files::isDirectory)
|
||||
.flatMap(DataPack::walk)
|
||||
.filter(path -> path.getFileName().toString().endsWith(".json"))
|
||||
.filter(Files::isRegularFile)
|
||||
.forEach(file -> loadResource(root, file, () -> {
|
||||
try (BufferedReader reader = Files.newBufferedReader(file)) {
|
||||
return ResourcesGson.INSTANCE.fromJson(reader, DimensionTypeData.class);
|
||||
}
|
||||
}, dimensionTypes));
|
||||
}
|
||||
|
||||
private <T> void loadResource(Path root, Path file, Loader<T> loader, Map<Key, T> resultMap) {
|
||||
try {
|
||||
ResourcePath<T> resourcePath = new ResourcePath<>(root.relativize(file));
|
||||
if (resultMap.containsKey(resourcePath)) return; // don't load already present resources
|
||||
|
||||
T resource = loader.load();
|
||||
if (resource == null) return; // don't load missing resources
|
||||
|
||||
resourcePath.setResource(resource);
|
||||
resultMap.put(resourcePath, resource);
|
||||
} catch (Exception ex) {
|
||||
Logger.global.logDebug("Failed to parse resource-file '" + file + "': " + ex);
|
||||
}
|
||||
}
|
||||
|
||||
public void bake() {
|
||||
dimensionTypes.putIfAbsent(new Key("minecraft", "overworld"), DimensionType.OVERWORLD);
|
||||
dimensionTypes.putIfAbsent(new Key("minecraft", "overworld_caves"), DimensionType.OVERWORLD_CAVES);
|
||||
dimensionTypes.putIfAbsent(new Key("minecraft", "the_nether"), DimensionType.NETHER);
|
||||
dimensionTypes.putIfAbsent(new Key("minecraft", "the_end"), DimensionType.END);
|
||||
}
|
||||
|
||||
private static Stream<Path> list(Path root) {
|
||||
if (!Files.isDirectory(root)) return Stream.empty();
|
||||
try {
|
||||
return Files.list(root);
|
||||
} catch (IOException ex) {
|
||||
throw new CompletionException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static Stream<Path> walk(Path root) {
|
||||
if (!Files.exists(root)) return Stream.empty();
|
||||
if (Files.isRegularFile(root)) return Stream.of(root);
|
||||
try {
|
||||
return Files.walk(root);
|
||||
} catch (IOException ex) {
|
||||
throw new CompletionException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private interface Loader<T> {
|
||||
T load() throws IOException;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
|
||||
}
|
|
@ -299,6 +299,7 @@ public class ResourcePack {
|
|||
}, BlueMap.THREAD_POOL),
|
||||
|
||||
// load biome configs
|
||||
// TODO: move this to datapacks?
|
||||
CompletableFuture.runAsync(() -> {
|
||||
list(root.resolve("assets"))
|
||||
.map(path -> path.resolve("biomes.json"))
|
||||
|
|
|
@ -27,9 +27,9 @@ package de.bluecolored.bluemap.core.resources.resourcepack.blockstate;
|
|||
import com.google.gson.Gson;
|
||||
import com.google.gson.annotations.JsonAdapter;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonToken;
|
||||
import de.bluecolored.bluemap.api.debug.DebugDump;
|
||||
import de.bluecolored.bluemap.core.resources.AbstractTypeAdapterFactory;
|
||||
import de.bluecolored.bluemap.core.resources.adapter.ResourcesGson;
|
||||
import de.bluecolored.bluemap.core.world.BlockState;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
|
@ -125,7 +125,7 @@ public class Multipart {
|
|||
andConditions.add(
|
||||
BlockStateCondition.and(andArray.toArray(new BlockStateCondition[0])));
|
||||
} else {
|
||||
String[] values = StringUtils.split(ResourcesGson.nextStringOrBoolean(in), '|');
|
||||
String[] values = StringUtils.split(nextStringOrBoolean(in), '|');
|
||||
andConditions.add(BlockStateCondition.property(name, values));
|
||||
}
|
||||
}
|
||||
|
@ -134,6 +134,11 @@ public class Multipart {
|
|||
return BlockStateCondition.and(andConditions.toArray(new BlockStateCondition[0]));
|
||||
}
|
||||
|
||||
private String nextStringOrBoolean(JsonReader in) throws IOException {
|
||||
if (in.peek() == JsonToken.BOOLEAN) return Boolean.toString(in.nextBoolean());
|
||||
return in.nextString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -24,16 +24,16 @@
|
|||
*/
|
||||
package de.bluecolored.bluemap.core.storage;
|
||||
|
||||
import java.io.FilterInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
public class CompressedInputStream extends InputStream {
|
||||
public class CompressedInputStream extends FilterInputStream {
|
||||
|
||||
private final InputStream in;
|
||||
private final Compression compression;
|
||||
|
||||
public CompressedInputStream(InputStream in, Compression compression) {
|
||||
this.in = in;
|
||||
super(in);
|
||||
this.compression = compression;
|
||||
}
|
||||
|
||||
|
@ -45,29 +45,4 @@ public class CompressedInputStream extends InputStream {
|
|||
return compression;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
return in.read();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] b, int off, int len) throws IOException {
|
||||
return in.read(b, off, len);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
in.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int available() throws IOException {
|
||||
return in.available();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() throws IOException {
|
||||
in.reset();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@ import de.bluecolored.bluemap.core.storage.CompressedInputStream;
|
|||
import de.bluecolored.bluemap.core.storage.Compression;
|
||||
import de.bluecolored.bluemap.core.storage.sql.dialect.Dialect;
|
||||
import de.bluecolored.bluemap.core.storage.sql.dialect.PostgresDialect;
|
||||
import de.bluecolored.bluemap.core.util.WrappedOutputStream;
|
||||
import de.bluecolored.bluemap.core.util.OnCloseOutputStream;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.MalformedURLException;
|
||||
|
@ -51,7 +51,7 @@ public class PostgreSQLStorage extends SQLStorage {
|
|||
public OutputStream writeMapTile(String mapId, int lod, Vector2i tile) throws IOException {
|
||||
Compression compression = lod == 0 ? this.hiresCompression : Compression.NONE;
|
||||
ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
|
||||
return new WrappedOutputStream(compression.compress(byteOut), () -> {
|
||||
return new OnCloseOutputStream(compression.compress(byteOut), () -> {
|
||||
int mapFK = getMapFK(mapId);
|
||||
int tileCompressionFK = getMapTileCompressionFK(compression);
|
||||
|
||||
|
@ -71,7 +71,7 @@ public class PostgreSQLStorage extends SQLStorage {
|
|||
@Override
|
||||
public OutputStream writeMeta(String mapId, String name) {
|
||||
ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
|
||||
return new WrappedOutputStream(byteOut, () -> {
|
||||
return new OnCloseOutputStream(byteOut, () -> {
|
||||
int mapFK = getMapFK(mapId);
|
||||
recoveringConnection(connection -> {
|
||||
executeUpdate(connection, this.dialect.writeMeta(),
|
||||
|
|
|
@ -32,7 +32,7 @@ import de.bluecolored.bluemap.core.logger.Logger;
|
|||
import de.bluecolored.bluemap.core.storage.*;
|
||||
import de.bluecolored.bluemap.core.storage.sql.dialect.DialectType;
|
||||
import de.bluecolored.bluemap.core.storage.sql.dialect.Dialect;
|
||||
import de.bluecolored.bluemap.core.util.WrappedOutputStream;
|
||||
import de.bluecolored.bluemap.core.util.OnCloseOutputStream;
|
||||
import org.apache.commons.dbcp2.*;
|
||||
import org.apache.commons.pool2.ObjectPool;
|
||||
import org.apache.commons.pool2.impl.GenericObjectPool;
|
||||
|
@ -108,7 +108,7 @@ public abstract class SQLStorage extends Storage {
|
|||
Compression compression = lod == 0 ? this.hiresCompression : Compression.NONE;
|
||||
|
||||
ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
|
||||
return new WrappedOutputStream(compression.compress(byteOut), () -> {
|
||||
return new OnCloseOutputStream(compression.compress(byteOut), () -> {
|
||||
int mapFK = getMapFK(mapId);
|
||||
int tileCompressionFK = getMapTileCompressionFK(compression);
|
||||
|
||||
|
@ -234,7 +234,7 @@ public abstract class SQLStorage extends Storage {
|
|||
@Override
|
||||
public OutputStream writeMeta(String mapId, String name) {
|
||||
ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
|
||||
return new WrappedOutputStream(byteOut, () -> {
|
||||
return new OnCloseOutputStream(byteOut, () -> {
|
||||
int mapFK = getMapFK(mapId);
|
||||
|
||||
recoveringConnection(connection -> {
|
||||
|
|
|
@ -40,7 +40,7 @@ public class FileHelper {
|
|||
final Path partFile = getPartFile(file);
|
||||
FileHelper.createDirectories(partFile.getParent());
|
||||
OutputStream os = Files.newOutputStream(partFile, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE);
|
||||
return new WrappedOutputStream(os, () -> {
|
||||
return new OnCloseOutputStream(os, () -> {
|
||||
if (!Files.exists(partFile)) return;
|
||||
FileHelper.createDirectories(file.getParent());
|
||||
FileHelper.move(partFile, file);
|
||||
|
|
|
@ -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;
|
||||
|
|
@ -73,7 +73,7 @@ public class Key {
|
|||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
Key that = (Key) o;
|
||||
return getFormatted() == that.getFormatted();
|
||||
return formatted == that.formatted;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -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;
|
|
@ -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;
|
|
@ -26,22 +26,50 @@ package de.bluecolored.bluemap.core.world;
|
|||
|
||||
public interface Chunk {
|
||||
|
||||
boolean isGenerated();
|
||||
Chunk EMPTY_CHUNK = new Chunk() {};
|
||||
|
||||
long getInhabitedTime();
|
||||
default boolean isGenerated() {
|
||||
return false;
|
||||
}
|
||||
|
||||
BlockState getBlockState(int x, int y, int z);
|
||||
default boolean hasLightData() {
|
||||
return false;
|
||||
}
|
||||
|
||||
LightData getLightData(int x, int y, int z, LightData target);
|
||||
default long getInhabitedTime() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
String getBiome(int x, int y, int z);
|
||||
default BlockState getBlockState(int x, int y, int z) {
|
||||
return BlockState.AIR;
|
||||
}
|
||||
|
||||
int getMaxY(int x, int z);
|
||||
default LightData getLightData(int x, int y, int z, LightData target) {
|
||||
return target.set(0, 0);
|
||||
}
|
||||
|
||||
int getMinY(int x, int z);
|
||||
default String getBiome(int x, int y, int z) {
|
||||
return Biome.DEFAULT.getFormatted();
|
||||
}
|
||||
|
||||
int getWorldSurfaceY(int x, int z);
|
||||
default int getMaxY(int x, int z) {
|
||||
return 255;
|
||||
}
|
||||
|
||||
int getOceanFloorY(int x, int z);
|
||||
default int getMinY(int x, int z) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
default boolean hasWorldSurfaceHeights() {
|
||||
return false;
|
||||
}
|
||||
|
||||
default int getWorldSurfaceY(int x, int z) { return 0; }
|
||||
|
||||
default boolean hasOceanFloorHeights() {
|
||||
return false;
|
||||
}
|
||||
|
||||
default int getOceanFloorY(int x, int z) { return 0; }
|
||||
|
||||
}
|
||||
|
|
|
@ -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.");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -24,34 +24,41 @@
|
|||
*/
|
||||
package de.bluecolored.bluemap.core.world;
|
||||
|
||||
import com.flowpowered.math.vector.Vector2i;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collection;
|
||||
|
||||
public interface Region {
|
||||
|
||||
/**
|
||||
* Returns a collection of all generated chunks.<br>
|
||||
* <i>(Be aware that the collection is not cached and recollected each time from the world-files!)</i>
|
||||
* Directly loads and returns the specified chunk.<br>
|
||||
* (implementations should consider overriding this method for a faster implementation)
|
||||
*/
|
||||
default Collection<Vector2i> listChunks(){
|
||||
return listChunks(0);
|
||||
default Chunk loadChunk(int chunkX, int chunkZ) throws IOException {
|
||||
class SingleChunkConsumer implements ChunkConsumer {
|
||||
private Chunk foundChunk = Chunk.EMPTY_CHUNK;
|
||||
|
||||
@Override
|
||||
public boolean filter(int x, int z, long lastModified) {
|
||||
return x == chunkX && z == chunkZ;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(int chunkX, int chunkZ, Chunk chunk) {
|
||||
this.foundChunk = chunk;
|
||||
}
|
||||
}
|
||||
|
||||
SingleChunkConsumer singleChunkConsumer = new SingleChunkConsumer();
|
||||
iterateAllChunks(singleChunkConsumer);
|
||||
return singleChunkConsumer.foundChunk;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a collection of all chunks that have been modified at or after the specified timestamp.<br>
|
||||
* <i>(Be aware that the collection is not cached and recollected each time from the world-files!)</i>
|
||||
* Iterates over all chunks in this region and first calls {@link ChunkConsumer#filter(int, int, long)}.<br>
|
||||
* And if (any only if) that method returned <code>true</code>, the chunk will be loaded and {@link ChunkConsumer#accept(int, int, Chunk)}
|
||||
* will be called with the loaded chunk.
|
||||
* @param consumer the consumer choosing which chunks to load and accepting them
|
||||
* @throws IOException if an IOException occurred trying to read the region
|
||||
*/
|
||||
Collection<Vector2i> listChunks(long modifiedSince);
|
||||
|
||||
default Chunk loadChunk(int chunkX, int chunkZ) throws IOException {
|
||||
return loadChunk(chunkX, chunkZ, false);
|
||||
}
|
||||
|
||||
Chunk loadChunk(int chunkX, int chunkZ, boolean ignoreMissingLightData) throws IOException;
|
||||
|
||||
Path getRegionFile();
|
||||
void iterateAllChunks(ChunkConsumer consumer) throws IOException;
|
||||
|
||||
}
|
||||
|
|
|
@ -26,10 +26,11 @@ package de.bluecolored.bluemap.core.world;
|
|||
|
||||
import com.flowpowered.math.vector.Vector2i;
|
||||
import com.flowpowered.math.vector.Vector3i;
|
||||
import de.bluecolored.bluemap.core.util.Grid;
|
||||
import de.bluecolored.bluemap.core.util.Key;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collection;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Represents a World on the Server<br>
|
||||
|
@ -38,17 +39,15 @@ import java.util.UUID;
|
|||
*/
|
||||
public interface World {
|
||||
|
||||
Path getSaveFolder();
|
||||
Path getWorldFolder();
|
||||
|
||||
Key getDimension();
|
||||
|
||||
String getName();
|
||||
|
||||
int getSkyLight();
|
||||
|
||||
Vector3i getSpawnPoint();
|
||||
|
||||
int getMaxY(int x, int z);
|
||||
|
||||
int getMinY(int x, int z);
|
||||
DimensionType getDimensionType();
|
||||
|
||||
Grid getChunkGrid();
|
||||
|
||||
|
@ -57,7 +56,7 @@ public interface World {
|
|||
/**
|
||||
* Returns the {@link Chunk} on the specified block-position
|
||||
*/
|
||||
Chunk getChunkAtBlock(int x, int y, int z);
|
||||
Chunk getChunkAtBlock(int x, int z);
|
||||
|
||||
/**
|
||||
* Returns the {@link Chunk} on the specified chunk-position
|
||||
|
@ -75,6 +74,11 @@ public interface World {
|
|||
*/
|
||||
Collection<Vector2i> listRegions();
|
||||
|
||||
/**
|
||||
* Loads all chunks from the specified region into the chunk cache (if there is a cache)
|
||||
*/
|
||||
void preloadRegionChunks(int x, int z);
|
||||
|
||||
/**
|
||||
* Invalidates the complete chunk cache (if there is a cache), so that every chunk has to be reloaded from disk
|
||||
*/
|
||||
|
|
|
@ -22,7 +22,12 @@
|
|||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
package de.bluecolored.bluemap.core.world;
|
||||
package de.bluecolored.bluemap.core.world.block;
|
||||
|
||||
import de.bluecolored.bluemap.core.world.BlockState;
|
||||
import de.bluecolored.bluemap.core.world.Chunk;
|
||||
import de.bluecolored.bluemap.core.world.LightData;
|
||||
import de.bluecolored.bluemap.core.world.World;
|
||||
|
||||
public class Block<T extends Block<T>> {
|
||||
|
||||
|
@ -131,7 +136,7 @@ public class Block<T extends Block<T>> {
|
|||
}
|
||||
|
||||
public Chunk getChunk() {
|
||||
if (chunk == null) chunk = world.getChunkAtBlock(x, y, z);
|
||||
if (chunk == null) chunk = world.getChunkAtBlock(x, z);
|
||||
return chunk;
|
||||
}
|
||||
|
|
@ -22,10 +22,11 @@
|
|||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
package de.bluecolored.bluemap.core.world;
|
||||
package de.bluecolored.bluemap.core.world.block;
|
||||
|
||||
import de.bluecolored.bluemap.core.map.hires.RenderSettings;
|
||||
import de.bluecolored.bluemap.core.resources.resourcepack.ResourcePack;
|
||||
import de.bluecolored.bluemap.core.world.World;
|
||||
|
||||
public class BlockNeighborhood<T extends BlockNeighborhood<T>> extends ExtendedBlock<T> {
|
||||
|
|
@ -22,10 +22,11 @@
|
|||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
package de.bluecolored.bluemap.core.world;
|
||||
package de.bluecolored.bluemap.core.world.block;
|
||||
|
||||
import de.bluecolored.bluemap.core.map.hires.RenderSettings;
|
||||
import de.bluecolored.bluemap.core.resources.resourcepack.ResourcePack;
|
||||
import de.bluecolored.bluemap.core.world.*;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
|
@ -36,6 +37,7 @@ public class ExtendedBlock<T extends ExtendedBlock<T>> extends Block<T> {
|
|||
private Biome biome;
|
||||
|
||||
private boolean insideRenderBoundsCalculated, insideRenderBounds;
|
||||
private boolean isCaveCalculated, isCave;
|
||||
|
||||
public ExtendedBlock(ResourcePack resourcePack, RenderSettings renderSettings, World world, int x, int y, int z) {
|
||||
super(world, x, y, z);
|
||||
|
@ -51,6 +53,7 @@ public class ExtendedBlock<T extends ExtendedBlock<T>> extends Block<T> {
|
|||
this.biome = null;
|
||||
|
||||
this.insideRenderBoundsCalculated = false;
|
||||
this.isCaveCalculated = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -62,7 +65,7 @@ public class ExtendedBlock<T extends ExtendedBlock<T>> extends Block<T> {
|
|||
@Override
|
||||
public LightData getLightData() {
|
||||
LightData ld = super.getLightData();
|
||||
if (renderSettings.isRenderEdges() && !isInsideRenderBounds()) ld.set(getWorld().getSkyLight(), ld.getBlockLight());
|
||||
if (renderSettings.isRenderEdges() && !isInsideRenderBounds()) ld.set(getWorld().getDimensionType().hasSkylight() ? 16 : 0, ld.getBlockLight());
|
||||
return ld;
|
||||
}
|
||||
|
||||
|
@ -90,6 +93,18 @@ public class ExtendedBlock<T extends ExtendedBlock<T>> extends Block<T> {
|
|||
return insideRenderBounds;
|
||||
}
|
||||
|
||||
public boolean isCave() {
|
||||
if (!isCaveCalculated) {
|
||||
isCave = getY() < renderSettings.getRemoveCavesBelowY() &&
|
||||
!getChunk().hasOceanFloorHeights() ||
|
||||
getY() < getChunk().getOceanFloorY(getX(), getZ()) +
|
||||
renderSettings.getCaveDetectionOceanFloor();
|
||||
isCaveCalculated = true;
|
||||
}
|
||||
|
||||
return isCave;
|
||||
}
|
||||
|
||||
public ResourcePack getResourcePack() {
|
||||
return resourcePack;
|
||||
}
|
|
@ -22,18 +22,21 @@
|
|||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
package de.bluecolored.bluemap.core.mca;
|
||||
package de.bluecolored.bluemap.core.world.mca;
|
||||
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import de.bluecolored.bluemap.core.mca.deserializer.BlockStateDeserializer;
|
||||
import de.bluecolored.bluemap.core.util.Key;
|
||||
import de.bluecolored.bluemap.core.world.BlockState;
|
||||
import de.bluecolored.bluemap.core.world.mca.data.BlockStateDeserializer;
|
||||
import de.bluecolored.bluemap.core.world.mca.data.KeyDeserializer;
|
||||
import de.bluecolored.bluenbt.BlueNBT;
|
||||
|
||||
public class MCAMath {
|
||||
public class MCAUtil {
|
||||
|
||||
public static final BlueNBT BLUENBT = new BlueNBT();
|
||||
static {
|
||||
BLUENBT.register(TypeToken.get(BlockState.class), new BlockStateDeserializer());
|
||||
BLUENBT.register(TypeToken.get(Key.class), new KeyDeserializer());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -53,17 +56,18 @@ public class MCAMath {
|
|||
/**
|
||||
* Treating the long array "data" as a continuous stream of bits, returning the "valueIndex"-th value when each value has "bitsPerValue" bits.
|
||||
*/
|
||||
@SuppressWarnings("ShiftOutOfRange")
|
||||
public static long getValueFromLongStream(long[] data, int valueIndex, int bitsPerValue) {
|
||||
int bitIndex = valueIndex * bitsPerValue;
|
||||
int firstLong = bitIndex >> 6; // index / 64
|
||||
int bitoffset = bitIndex & 0x3F; // Math.floorMod(index, 64)
|
||||
int bitOffset = bitIndex & 0x3F; // Math.floorMod(index, 64)
|
||||
|
||||
if (firstLong >= data.length) return 0;
|
||||
long value = data[firstLong] >>> bitoffset;
|
||||
long value = data[firstLong] >>> bitOffset;
|
||||
|
||||
if (bitoffset > 0 && firstLong + 1 < data.length) {
|
||||
if (bitOffset > 0 && firstLong + 1 < data.length) {
|
||||
long value2 = data[firstLong + 1];
|
||||
value2 = value2 << -bitoffset;
|
||||
value2 = value2 << -bitOffset;
|
||||
value = value | value2;
|
||||
}
|
||||
|
|
@ -0,0 +1,253 @@
|
|||
package de.bluecolored.bluemap.core.world.mca;
|
||||
|
||||
import com.flowpowered.math.vector.Vector2i;
|
||||
import com.flowpowered.math.vector.Vector3i;
|
||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||
import com.github.benmanes.caffeine.cache.LoadingCache;
|
||||
import de.bluecolored.bluemap.core.BlueMap;
|
||||
import de.bluecolored.bluemap.core.logger.Logger;
|
||||
import de.bluecolored.bluemap.core.resources.datapack.DataPack;
|
||||
import de.bluecolored.bluemap.core.util.Grid;
|
||||
import de.bluecolored.bluemap.core.util.Key;
|
||||
import de.bluecolored.bluemap.core.util.Vector2iCache;
|
||||
import de.bluecolored.bluemap.core.world.*;
|
||||
import de.bluecolored.bluemap.core.world.mca.chunk.ChunkLoader;
|
||||
import de.bluecolored.bluemap.core.world.mca.data.LevelData;
|
||||
import de.bluecolored.bluemap.core.world.mca.region.RegionType;
|
||||
import lombok.Getter;
|
||||
import lombok.ToString;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
@Getter
|
||||
@ToString
|
||||
public class MCAWorld implements World {
|
||||
|
||||
private static final Grid CHUNK_GRID = new Grid(16);
|
||||
private static final Grid REGION_GRID = new Grid(32).multiply(CHUNK_GRID);
|
||||
|
||||
private static final Vector2iCache VECTOR_2_I_CACHE = new Vector2iCache();
|
||||
|
||||
private final Path worldFolder;
|
||||
private final Key dimension;
|
||||
private final LevelData levelData;
|
||||
private final DataPack dataPack;
|
||||
|
||||
private final DimensionType dimensionType;
|
||||
private final Vector3i spawnPoint;
|
||||
private final Path dimensionFolder;
|
||||
private final Path regionFolder;
|
||||
|
||||
private final ChunkLoader chunkLoader = new ChunkLoader();
|
||||
private final LoadingCache<Vector2i, Region> regionCache = Caffeine.newBuilder()
|
||||
.executor(BlueMap.THREAD_POOL)
|
||||
.maximumSize(64)
|
||||
.expireAfterWrite(10, TimeUnit.MINUTES)
|
||||
.build(this::loadRegion);
|
||||
private final LoadingCache<Vector2i, Chunk> chunkCache = Caffeine.newBuilder()
|
||||
.executor(BlueMap.THREAD_POOL)
|
||||
.maximumSize(2048) // 2 regions worth of chunks
|
||||
.expireAfterWrite(10, TimeUnit.MINUTES)
|
||||
.build(this::loadChunk);
|
||||
|
||||
public MCAWorld(Path worldFolder, Key dimension, LevelData levelData, DataPack dataPack) {
|
||||
this.worldFolder = worldFolder;
|
||||
this.dimension = dimension;
|
||||
this.levelData = levelData;
|
||||
this.dataPack = dataPack;
|
||||
|
||||
LevelData.Dimension dim = levelData.getData().getWorldGenSettings().getDimensions().get(dimension.getFormatted());
|
||||
if (dim == null) {
|
||||
Logger.global.logWarning("The level-data does not contain any dimension with the id '" + dimension +
|
||||
"', using fallback.");
|
||||
dim = new LevelData.Dimension();
|
||||
}
|
||||
DimensionType dimensionType = dataPack.getDimensionType(new Key(dim.getType()));
|
||||
if (dimensionType == null) {
|
||||
Logger.global.logWarning("The data-pack for world '" + worldFolder +
|
||||
"' does not contain any dimension-type with the id '" + dim.getType() + "', using fallback.");
|
||||
dimensionType = DimensionType.OVERWORLD;
|
||||
}
|
||||
|
||||
this.dimensionType = dimensionType;
|
||||
this.spawnPoint = new Vector3i(
|
||||
levelData.getData().getSpawnX(),
|
||||
levelData.getData().getSpawnY(),
|
||||
levelData.getData().getSpawnZ()
|
||||
);
|
||||
this.dimensionFolder = resolveDimensionFolder(worldFolder, dimension);
|
||||
this.regionFolder = getWorldFolder().resolve("region");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return levelData.getData().getLevelName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Grid getChunkGrid() {
|
||||
return CHUNK_GRID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Grid getRegionGrid() {
|
||||
return REGION_GRID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Chunk getChunkAtBlock(int x, int z) {
|
||||
return getChunk(x >> 4, z >> 4);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Chunk getChunk(int x, int z) {
|
||||
return getChunk(VECTOR_2_I_CACHE.get(x, z));
|
||||
}
|
||||
|
||||
private Chunk getChunk(Vector2i pos) {
|
||||
return chunkCache.get(pos);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Region getRegion(int x, int z) {
|
||||
return getRegion(VECTOR_2_I_CACHE.get(x, z));
|
||||
}
|
||||
|
||||
private Region getRegion(Vector2i pos) {
|
||||
return regionCache.get(pos);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Vector2i> listRegions() {
|
||||
File[] regionFiles = getRegionFolder().toFile().listFiles();
|
||||
if (regionFiles == null) return Collections.emptyList();
|
||||
|
||||
List<Vector2i> regions = new ArrayList<>(regionFiles.length);
|
||||
|
||||
for (File file : regionFiles) {
|
||||
if (RegionType.forFileName(file.getName()) == null) continue;
|
||||
if (file.length() <= 0) continue;
|
||||
|
||||
try {
|
||||
String[] filenameParts = file.getName().split("\\.");
|
||||
int rX = Integer.parseInt(filenameParts[1]);
|
||||
int rZ = Integer.parseInt(filenameParts[2]);
|
||||
|
||||
regions.add(new Vector2i(rX, rZ));
|
||||
} catch (NumberFormatException ignore) {}
|
||||
}
|
||||
|
||||
return regions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preloadRegionChunks(int x, int z) {
|
||||
try {
|
||||
getRegion(x, z).iterateAllChunks((cx, cz, chunk) -> {
|
||||
Vector2i chunkPos = VECTOR_2_I_CACHE.get(cx, cz);
|
||||
chunkCache.put(chunkPos, chunk);
|
||||
});
|
||||
} catch (IOException ex) {
|
||||
Logger.global.logDebug("Unexpected exception trying to load preload region (x:" + x + ", z:" + z + "):" + ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateChunkCache() {
|
||||
chunkCache.invalidateAll();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateChunkCache(int x, int z) {
|
||||
chunkCache.invalidate(VECTOR_2_I_CACHE.get(x, z));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cleanUpChunkCache() {
|
||||
chunkCache.cleanUp();
|
||||
}
|
||||
|
||||
private Region loadRegion(Vector2i regionPos) {
|
||||
return loadRegion(regionPos.getX(), regionPos.getY());
|
||||
}
|
||||
|
||||
private Region loadRegion(int x, int z) {
|
||||
return RegionType.loadRegion(this, getRegionFolder(), x, z);
|
||||
}
|
||||
|
||||
private Chunk loadChunk(Vector2i chunkPos) {
|
||||
return loadChunk(chunkPos.getX(), chunkPos.getY());
|
||||
}
|
||||
|
||||
private Chunk loadChunk(int x, int z) {
|
||||
final int tries = 3;
|
||||
final int tryInterval = 1000;
|
||||
|
||||
Exception loadException = null;
|
||||
for (int i = 0; i < tries; i++) {
|
||||
try {
|
||||
return getRegion(x >> 5, z >> 5)
|
||||
.loadChunk(x, z);
|
||||
} catch (IOException | RuntimeException e) {
|
||||
if (loadException != null) e.addSuppressed(loadException);
|
||||
loadException = e;
|
||||
|
||||
if (i + 1 < tries) {
|
||||
try {
|
||||
Thread.sleep(tryInterval);
|
||||
} catch (InterruptedException ex) {
|
||||
Thread.currentThread().interrupt();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Logger.global.logDebug("Unexpected exception trying to load chunk (x:" + x + ", z:" + z + "):" + loadException);
|
||||
return Chunk.EMPTY_CHUNK;
|
||||
}
|
||||
|
||||
public static MCAWorld load(Path worldFolder, Key dimension) throws IOException {
|
||||
|
||||
// load level.dat
|
||||
Path levelFile = worldFolder.resolve("level.dat");
|
||||
InputStream levelFileIn = new GZIPInputStream(new BufferedInputStream(Files.newInputStream(levelFile)));
|
||||
LevelData levelData = MCAUtil.BLUENBT.read(levelFileIn, LevelData.class);
|
||||
|
||||
// load datapacks
|
||||
DataPack dataPack = new DataPack();
|
||||
Path dataPackFolder = worldFolder.resolve("datapacks");
|
||||
if (Files.exists(dataPackFolder)) {
|
||||
List<Path> roots;
|
||||
try (var stream = Files.list(dataPackFolder)) {
|
||||
roots = stream
|
||||
.sorted(Comparator.reverseOrder())
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
for (Path root : roots) {
|
||||
dataPack.load(root);
|
||||
}
|
||||
}
|
||||
dataPack.bake();
|
||||
|
||||
// create world
|
||||
return new MCAWorld(worldFolder, dimension, levelData, dataPack);
|
||||
}
|
||||
|
||||
private static Path resolveDimensionFolder(Path worldFolder, Key dimension) {
|
||||
if (DataPack.DIMENSION_OVERWORLD.equals(dimension)) return worldFolder;
|
||||
if (DataPack.DIMENSION_THE_NETHER.equals(dimension)) return worldFolder.resolve("DIM-1");
|
||||
if (DataPack.DIMENSION_THE_END.equals(dimension)) return worldFolder.resolve("DIM1");
|
||||
return worldFolder.resolve("dimensions").resolve(dimension.getNamespace()).resolve(dimension.getValue());
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
package de.bluecolored.bluemap.core.world.mca.chunk;
|
||||
|
||||
import de.bluecolored.bluemap.core.storage.Compression;
|
||||
import de.bluecolored.bluemap.core.world.mca.MCAUtil;
|
||||
import de.bluecolored.bluemap.core.world.mca.region.MCARegion;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.List;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
public class ChunkLoader {
|
||||
|
||||
// sorted list of chunk-versions, loaders at the start of the list are preferred over loaders at the end
|
||||
private static final List<ChunkVersionLoader<?>> CHUNK_VERSION_LOADERS = List.of(
|
||||
new ChunkVersionLoader<>(Chunk_1_18.Data.class, Chunk_1_18::new, 0)
|
||||
);
|
||||
|
||||
private ChunkVersionLoader<?> lastUsedLoader = CHUNK_VERSION_LOADERS.get(0);
|
||||
|
||||
public MCAChunk load(MCARegion region, byte[] data, int offset, int length, Compression compression) throws IOException {
|
||||
InputStream in = new ByteArrayInputStream(data, offset, length);
|
||||
in.mark(-1);
|
||||
|
||||
// try last used version
|
||||
ChunkVersionLoader<?> usedLoader = lastUsedLoader;
|
||||
MCAChunk chunk = usedLoader.load(region, compression.decompress(in));
|
||||
|
||||
// check version and reload chunk if the wrong loader has been used and a better one has been found
|
||||
ChunkVersionLoader<?> actualLoader = findBestLoaderForVersion(chunk.getDataVersion());
|
||||
if (actualLoader != null && usedLoader != actualLoader) {
|
||||
in.reset(); // reset read position
|
||||
chunk = actualLoader.load(region, compression.decompress(in));
|
||||
lastUsedLoader = actualLoader;
|
||||
}
|
||||
|
||||
return chunk;
|
||||
}
|
||||
|
||||
private @Nullable ChunkVersionLoader<?> findBestLoaderForVersion(int version) {
|
||||
for (ChunkVersionLoader<?> loader : CHUNK_VERSION_LOADERS) {
|
||||
if (loader.mightSupport(version)) return loader;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@Getter
|
||||
private static class ChunkVersionLoader<D extends MCAChunk.Data> {
|
||||
|
||||
private final Class<D> dataType;
|
||||
private final BiFunction<MCARegion, D, MCAChunk> constructor;
|
||||
private final int dataVersion;
|
||||
|
||||
public MCAChunk load(MCARegion region, InputStream in) throws IOException {
|
||||
D data = MCAUtil.BLUENBT.read(in, dataType);
|
||||
return mightSupport(data.getDataVersion()) ? constructor.apply(region, data) : new MCAChunk(region, data) {};
|
||||
}
|
||||
|
||||
public boolean mightSupport(int dataVersion) {
|
||||
return dataVersion >= this.dataVersion;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package de.bluecolored.bluemap.core.mca.deserializer;
|
||||
package de.bluecolored.bluemap.core.world.mca.data;
|
||||
|
||||
import de.bluecolored.bluemap.core.world.BlockState;
|
||||
import de.bluecolored.bluenbt.NBTReader;
|
||||
|
@ -18,27 +18,23 @@ public class BlockStateDeserializer implements TypeDeserializer<BlockState> {
|
|||
Map<String, String> properties = null;
|
||||
|
||||
while (reader.hasNext()) {
|
||||
String name = reader.name();
|
||||
if (name.equals("Name")){
|
||||
id = reader.nextString();
|
||||
} else if (name.equals("Properties")) {
|
||||
properties = new LinkedHashMap<>();
|
||||
reader.beginCompound();
|
||||
while (reader.hasNext())
|
||||
properties.put(reader.name(), reader.nextString());
|
||||
reader.endCompound();
|
||||
} else {
|
||||
reader.skip();
|
||||
switch (reader.name()) {
|
||||
case "Name" : id = reader.nextString(); break;
|
||||
case "Properties" :
|
||||
properties = new LinkedHashMap<>();
|
||||
reader.beginCompound();
|
||||
while (reader.hasNext())
|
||||
properties.put(reader.name(), reader.nextString());
|
||||
reader.endCompound();
|
||||
break;
|
||||
default : reader.skip();
|
||||
}
|
||||
}
|
||||
|
||||
reader.endCompound();
|
||||
|
||||
if (id == null) throw new IOException("Invalid BlockState, Name is missing!");
|
||||
|
||||
if (properties == null)
|
||||
return new BlockState(id);
|
||||
return new BlockState(id, properties);
|
||||
return properties == null ? new BlockState(id) : new BlockState(id, properties);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
package de.bluecolored.bluemap.core.world.mca.data;
|
||||
|
||||
import de.bluecolored.bluemap.core.util.Key;
|
||||
import de.bluecolored.bluenbt.NBTReader;
|
||||
import de.bluecolored.bluenbt.TypeDeserializer;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class KeyDeserializer implements TypeDeserializer<Key> {
|
||||
|
||||
@Override
|
||||
public Key read(NBTReader reader) throws IOException {
|
||||
return new Key(reader.nextString());
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
package de.bluecolored.bluemap.core.world.mca.data;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@Getter
|
||||
@SuppressWarnings("FieldMayBeFinal")
|
||||
public class LevelData {
|
||||
|
||||
private Data data = new Data();
|
||||
|
||||
@Getter
|
||||
public static class Data {
|
||||
private String levelName = "world";
|
||||
private int spawnX = 0, spawnY = 0, spawnZ = 0;
|
||||
private WGSettings worldGenSettings = new WGSettings();
|
||||
}
|
||||
|
||||
@Getter
|
||||
public static class WGSettings {
|
||||
private Map<String, Dimension> dimensions = new HashMap<>();
|
||||
}
|
||||
|
||||
@Getter
|
||||
public static class Dimension {
|
||||
private String type = "minecraft:overworld";
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,188 @@
|
|||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
package de.bluecolored.bluemap.core.world.mca.region;
|
||||
|
||||
import com.flowpowered.math.vector.Vector2i;
|
||||
import de.bluecolored.bluemap.core.storage.Compression;
|
||||
import de.bluecolored.bluemap.core.world.Chunk;
|
||||
import de.bluecolored.bluemap.core.world.ChunkConsumer;
|
||||
import de.bluecolored.bluemap.core.world.Region;
|
||||
import de.bluecolored.bluemap.core.world.mca.MCAWorld;
|
||||
import de.bluecolored.bluemap.core.world.mca.chunk.MCAChunk;
|
||||
import lombok.Getter;
|
||||
import lombok.ToString;
|
||||
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
|
||||
@Getter
|
||||
@ToString
|
||||
public class MCARegion implements Region {
|
||||
|
||||
public static final String FILE_SUFFIX = ".mca";
|
||||
|
||||
private final MCAWorld world;
|
||||
private final Path regionFile;
|
||||
private final Vector2i regionPos;
|
||||
|
||||
public MCARegion(MCAWorld world, Path regionFile) throws IllegalArgumentException {
|
||||
this.world = world;
|
||||
this.regionFile = regionFile;
|
||||
|
||||
String[] filenameParts = regionFile.getFileName().toString().split("\\.");
|
||||
int rX = Integer.parseInt(filenameParts[1]);
|
||||
int rZ = Integer.parseInt(filenameParts[2]);
|
||||
|
||||
this.regionPos = new Vector2i(rX, rZ);
|
||||
}
|
||||
|
||||
public MCARegion(MCAWorld world, Vector2i regionPos) throws IllegalArgumentException {
|
||||
this.world = world;
|
||||
this.regionPos = regionPos;
|
||||
this.regionFile = world.getRegionFolder().resolve(getRegionFileName(regionPos.getX(), regionPos.getY()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Chunk loadChunk(int chunkX, int chunkZ) throws IOException {
|
||||
if (Files.notExists(regionFile)) return Chunk.EMPTY_CHUNK;
|
||||
|
||||
long fileLength = Files.size(regionFile);
|
||||
if (fileLength == 0) return Chunk.EMPTY_CHUNK;
|
||||
|
||||
try (FileChannel channel = FileChannel.open(regionFile, StandardOpenOption.READ)) {
|
||||
int xzChunk = (chunkZ & 0b11111) << 5 | (chunkX & 0b11111);
|
||||
|
||||
byte[] header = new byte[4];
|
||||
channel.position(xzChunk * 4);
|
||||
readFully(channel, header, 0, 4);
|
||||
|
||||
int offset = header[0] << 16;
|
||||
offset |= (header[1] & 0xFF) << 8;
|
||||
offset |= header[2] & 0xFF;
|
||||
offset *= 4096;
|
||||
int size = header[3] * 4096;
|
||||
|
||||
if (size == 0) return Chunk.EMPTY_CHUNK;
|
||||
return loadChunk(channel, offset, size, new byte[size]);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void iterateAllChunks(ChunkConsumer consumer) throws IOException {
|
||||
if (Files.notExists(regionFile)) return;
|
||||
|
||||
long fileLength = Files.size(regionFile);
|
||||
if (fileLength == 0) return;
|
||||
|
||||
int chunkStartX = regionPos.getX() * 32;
|
||||
int chunkStartZ = regionPos.getY() * 32;
|
||||
|
||||
try (FileChannel channel = FileChannel.open(regionFile, StandardOpenOption.READ)) {
|
||||
byte[] header = new byte[1024 * 8];
|
||||
byte[] chunkDataBuffer = null;
|
||||
|
||||
// read the header
|
||||
readFully(channel, header, 0, header.length);
|
||||
|
||||
// iterate over all chunks
|
||||
for (int x = 0; x < 32; x++) {
|
||||
for (int z = 0; z < 32; z++) {
|
||||
int xzChunk = z * 32 + x;
|
||||
|
||||
int size = header[xzChunk * 4 + 3] * 4096;
|
||||
if (size == 0) continue;
|
||||
|
||||
int chunkX = chunkStartX + x;
|
||||
int chunkZ = chunkStartZ + z;
|
||||
|
||||
int i = xzChunk * 4 + 4096;
|
||||
int timestamp = header[i++] << 24;
|
||||
timestamp |= (header[i++] & 0xFF) << 16;
|
||||
timestamp |= (header[i++] & 0xFF) << 8;
|
||||
timestamp |= header[i] & 0xFF;
|
||||
|
||||
// load chunk only if consumers filter returns true
|
||||
if (consumer.filter(chunkX, chunkZ, timestamp)) {
|
||||
i = xzChunk * 4;
|
||||
int offset = header[i++] << 16;
|
||||
offset |= (header[i++] & 0xFF) << 8;
|
||||
offset |= header[i] & 0xFF;
|
||||
offset *= 4096;
|
||||
|
||||
if (chunkDataBuffer == null || chunkDataBuffer.length < size)
|
||||
chunkDataBuffer = new byte[size];
|
||||
|
||||
MCAChunk chunk = loadChunk(channel, offset, size, chunkDataBuffer);
|
||||
consumer.accept(chunkX, chunkZ, chunk);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private MCAChunk loadChunk(FileChannel channel, int offset, int size, byte[] dataBuffer) throws IOException {
|
||||
channel.position(offset);
|
||||
readFully(channel, dataBuffer, 0, size);
|
||||
|
||||
int compressionTypeId = dataBuffer[4];
|
||||
Compression compression;
|
||||
switch (compressionTypeId) {
|
||||
case 0 :
|
||||
case 3 : compression = Compression.NONE; break;
|
||||
case 1 : compression = Compression.GZIP; break;
|
||||
case 2 : compression = Compression.DEFLATE; break;
|
||||
default: throw new IOException("Unknown chunk compression-id: " + compressionTypeId);
|
||||
}
|
||||
|
||||
return world.getChunkLoader().load(this, dataBuffer, 5, size - 5, compression);
|
||||
}
|
||||
|
||||
public static String getRegionFileName(int regionX, int regionZ) {
|
||||
return "r." + regionX + "." + regionZ + FILE_SUFFIX;
|
||||
}
|
||||
|
||||
@SuppressWarnings("SameParameterValue")
|
||||
private static void readFully(ReadableByteChannel src, byte[] dst, int off, int len) throws IOException {
|
||||
readFully(src, ByteBuffer.wrap(dst), off, len);
|
||||
}
|
||||
|
||||
private static void readFully(ReadableByteChannel src, ByteBuffer bb, int off, int len) throws IOException {
|
||||
int n = 0;
|
||||
while (n < len) {
|
||||
bb.limit(Math.min(off + len, bb.capacity()));
|
||||
bb.position(off);
|
||||
int count = src.read(bb);
|
||||
if (count < 0) throw new EOFException();
|
||||
n += count;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -22,10 +22,10 @@
|
|||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
package de.bluecolored.bluemap.core.mca.region;
|
||||
package de.bluecolored.bluemap.core.world.mca.region;
|
||||
|
||||
import de.bluecolored.bluemap.core.mca.MCAWorld;
|
||||
import de.bluecolored.bluemap.core.world.Region;
|
||||
import de.bluecolored.bluemap.core.world.mca.MCAWorld;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
|
@ -34,8 +34,8 @@ import java.nio.file.Path;
|
|||
|
||||
public enum RegionType {
|
||||
|
||||
MCA (MCARegion::new, MCARegion.FILE_SUFFIX, MCARegion::getRegionFileName),
|
||||
LINEAR (LinearRegion::new, LinearRegion.FILE_SUFFIX, LinearRegion::getRegionFileName);
|
||||
MCA (MCARegion::new, MCARegion.FILE_SUFFIX, MCARegion::getRegionFileName);
|
||||
//LINEAR (LinearRegion::new, LinearRegion.FILE_SUFFIX, LinearRegion::getRegionFileName);
|
||||
|
||||
// we do this to improve performance, as calling values() creates a new array each time
|
||||
private final static RegionType[] VALUES = values();
|
|
@ -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;
|
||||
|
|
|
@ -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")
|
||||
|
|
Loading…
Reference in New Issue