Improve Version-Management and add support for variable world-heights to be able to support custom 1.17 worlds

This commit is contained in:
Blue (Lukas Rieger) 2021-06-04 11:25:05 +02:00
parent ecef2c4996
commit 67a0e2a0bf
No known key found for this signature in database
GPG Key ID: 904C4995F9E1F800
11 changed files with 101 additions and 66 deletions

View File

@ -36,7 +36,7 @@ public class MinecraftVersion implements Comparable<MinecraftVersion> {
private static final Pattern VERSION_REGEX = Pattern.compile("(?:(?<major>\\d+)\\.(?<minor>\\d+))(?:\\.(?<patch>\\d+))?(?:\\-(?:pre|rc)\\d+)?"); private static final Pattern VERSION_REGEX = Pattern.compile("(?:(?<major>\\d+)\\.(?<minor>\\d+))(?:\\.(?<patch>\\d+))?(?:\\-(?:pre|rc)\\d+)?");
public static final MinecraftVersion LATEST_SUPPORTED = new MinecraftVersion(1, 16, 5); public static final MinecraftVersion LATEST_SUPPORTED = new MinecraftVersion(1, 17, 0);
public static final MinecraftVersion EARLIEST_SUPPORTED = new MinecraftVersion(1, 12, 2); public static final MinecraftVersion EARLIEST_SUPPORTED = new MinecraftVersion(1, 12, 2);
public static final MinecraftVersion THE_FLATTENING = new MinecraftVersion(1, 13); public static final MinecraftVersion THE_FLATTENING = new MinecraftVersion(1, 13);
@ -145,7 +145,8 @@ public enum MinecraftResource {
MC_1_14 (new MinecraftVersion(1, 14), "mc1_13", "https://launcher.mojang.com/v1/objects/8c325a0c5bd674dd747d6ebaa4c791fd363ad8a9/client.jar"), MC_1_14 (new MinecraftVersion(1, 14), "mc1_13", "https://launcher.mojang.com/v1/objects/8c325a0c5bd674dd747d6ebaa4c791fd363ad8a9/client.jar"),
MC_1_15 (new MinecraftVersion(1, 15), "mc1_15", "https://launcher.mojang.com/v1/objects/e3f78cd16f9eb9a52307ed96ebec64241cc5b32d/client.jar"), MC_1_15 (new MinecraftVersion(1, 15), "mc1_15", "https://launcher.mojang.com/v1/objects/e3f78cd16f9eb9a52307ed96ebec64241cc5b32d/client.jar"),
MC_1_16 (new MinecraftVersion(1, 16), "mc1_16", "https://launcher.mojang.com/v1/objects/228fdf45541c4c2fe8aec4f20e880cb8fcd46621/client.jar"), MC_1_16 (new MinecraftVersion(1, 16), "mc1_16", "https://launcher.mojang.com/v1/objects/228fdf45541c4c2fe8aec4f20e880cb8fcd46621/client.jar"),
MC_1_16_2 (new MinecraftVersion(1, 16, 2), "mc1_16", "https://launcher.mojang.com/v1/objects/653e97a2d1d76f87653f02242d243cdee48a5144/client.jar"); MC_1_16_2 (new MinecraftVersion(1, 16, 2), "mc1_16", "https://launcher.mojang.com/v1/objects/653e97a2d1d76f87653f02242d243cdee48a5144/client.jar"),
MC_1_17_0_PRE (new MinecraftVersion(1, 17, -1), "mc1_16", "https://launcher.mojang.com/v1/objects/de8e68ea23f837f9ab628cda7b16ba3de4b79153/client.jar");
private final MinecraftVersion version; private final MinecraftVersion version;
private final String resourcePrefix; private final String resourcePrefix;

View File

@ -69,8 +69,8 @@ public HiresModel render(World world, Vector2i tile) {
Vector2i tileMin = tileGrid.getCellMin(tile); Vector2i tileMin = tileGrid.getCellMin(tile);
Vector2i tileMax = tileGrid.getCellMax(tile); Vector2i tileMax = tileGrid.getCellMax(tile);
Vector3i modelMin = new Vector3i(tileMin.getX(), world.getMinY(), tileMin.getY()); Vector3i modelMin = new Vector3i(tileMin.getX(), Integer.MIN_VALUE, tileMin.getY());
Vector3i modelMax = new Vector3i(tileMax.getX(), world.getMaxY(), tileMax.getY()); Vector3i modelMax = new Vector3i(tileMax.getX(), Integer.MAX_VALUE, tileMax.getY());
HiresModel model = renderer.render(world, modelMin, modelMax); HiresModel model = renderer.render(world, modelMin, modelMax);
save(model, tile); save(model, tile);

View File

@ -58,6 +58,7 @@ public HiresModelRenderer(ResourcePack resourcePack, RenderSettings renderSettin
public HiresModel render(World world, Vector3i modelMin, Vector3i modelMax) { public HiresModel render(World world, Vector3i modelMin, Vector3i modelMax) {
Vector3i min = modelMin.max(renderSettings.getMin()); Vector3i min = modelMin.max(renderSettings.getMin());
Vector3i max = modelMax.min(renderSettings.getMax()); Vector3i max = modelMax.min(renderSettings.getMax());
Vector3f modelAnchor = new Vector3f(modelMin.getX(), 0, modelMin.getZ());
HiresModel model = new HiresModel(world.getUUID(), modelMin, modelMax); HiresModel model = new HiresModel(world.getUUID(), modelMin, modelMax);
@ -66,8 +67,11 @@ public HiresModel render(World world, Vector3i modelMin, Vector3i modelMax) {
int maxHeight = 0; int maxHeight = 0;
Vector4f color = Vector4f.ZERO; Vector4f color = Vector4f.ZERO;
for (int y = min.getY(); y <= max.getY(); y++){ int minY = Math.max(min.getY(), world.getMinY(x, z));
int maxY = Math.min(max.getY(), world.getMaxY(x, z));
for (int y = minY; y <= maxY; y++){
Block block = world.getBlock(x, y, z); Block block = world.getBlock(x, y, z);
if (block.getBlockState().equals(BlockState.AIR)) continue; if (block.getBlockState().equals(BlockState.AIR)) continue;
@ -83,8 +87,12 @@ public HiresModel render(World world, Vector3i modelMin, Vector3i modelMax) {
} }
//Logger.global.noFloodDebug(block.getBlockState().getFullId() + "-hiresModelRenderer-blockmodelerr", "Failed to create BlockModel for BlockState: " + block.getBlockState() + " (" + e.toString() + ")"); //Logger.global.noFloodDebug(block.getBlockState().getFullId() + "-hiresModelRenderer-blockmodelerr", "Failed to create BlockModel for BlockState: " + block.getBlockState() + " (" + e.toString() + ")");
} }
blockModel.translate(new Vector3f(x, y, z).sub(modelMin.toFloat())); // skip empty blocks
if (blockModel.getFaces().isEmpty()) continue;
// move block-model to correct position
blockModel.translate(new Vector3f(x - modelAnchor.getX(), y - modelAnchor.getY(), z - modelAnchor.getZ()));
//update color and height (only if not 100% translucent) //update color and height (only if not 100% translucent)
Vector4f blockColor = blockModel.getMapColor(); Vector4f blockColor = blockModel.getMapColor();

View File

@ -33,7 +33,6 @@
import net.querz.nbt.CompoundTag; import net.querz.nbt.CompoundTag;
import net.querz.nbt.ListTag; import net.querz.nbt.ListTag;
import net.querz.nbt.NumberTag; import net.querz.nbt.NumberTag;
import net.querz.nbt.mca.MCAUtil;
import java.util.Arrays; import java.util.Arrays;
import java.util.function.IntFunction; import java.util.function.IntFunction;
@ -90,7 +89,8 @@ public boolean isGenerated() {
@Override @Override
public BlockState getBlockState(Vector3i pos) { public BlockState getBlockState(Vector3i pos) {
int sectionY = MCAUtil.blockToChunk(pos.getY()); int sectionY = pos.getY() >> 4;
if (sectionY < 0 || sectionY >= this.sections.length) return BlockState.AIR;
Section section = this.sections[sectionY]; Section section = this.sections[sectionY];
if (section == null) return BlockState.AIR; if (section == null) return BlockState.AIR;
@ -99,7 +99,8 @@ public BlockState getBlockState(Vector3i pos) {
} }
public String getBlockIdMeta(Vector3i pos) { public String getBlockIdMeta(Vector3i pos) {
int sectionY = MCAUtil.blockToChunk(pos.getY()); int sectionY = pos.getY() >> 4;
if (sectionY < 0 || sectionY >= this.sections.length) return "0:0";
Section section = this.sections[sectionY]; Section section = this.sections[sectionY];
if (section == null) return "0:0"; if (section == null) return "0:0";
@ -110,8 +111,10 @@ public String getBlockIdMeta(Vector3i pos) {
@Override @Override
public LightData getLightData(Vector3i pos) { public LightData getLightData(Vector3i pos) {
if (!hasLight) return LightData.SKY; if (!hasLight) return LightData.SKY;
int sectionY = MCAUtil.blockToChunk(pos.getY()); int sectionY = pos.getY() >> 4;
if (sectionY < 0 || sectionY >= this.sections.length)
return (pos.getY() < 0) ? LightData.ZERO : LightData.SKY;
Section section = this.sections[sectionY]; Section section = this.sections[sectionY];
if (section == null) return LightData.SKY; if (section == null) return LightData.SKY;
@ -124,7 +127,8 @@ public Biome getBiome(int x, int y, int z) {
x = x & 0xF; // Math.floorMod(pos.getX(), 16) x = x & 0xF; // Math.floorMod(pos.getX(), 16)
z = z & 0xF; z = z & 0xF;
int biomeByteIndex = z * 16 + x; int biomeByteIndex = z * 16 + x;
if (biomeByteIndex >= this.biomes.length) return Biome.DEFAULT;
return biomeIdMapper.get(biomes[biomeByteIndex] & 0xFF); return biomeIdMapper.get(biomes[biomeByteIndex] & 0xFF);
} }

View File

@ -31,7 +31,6 @@
import de.bluecolored.bluemap.core.world.BlockState; import de.bluecolored.bluemap.core.world.BlockState;
import de.bluecolored.bluemap.core.world.LightData; import de.bluecolored.bluemap.core.world.LightData;
import net.querz.nbt.*; import net.querz.nbt.*;
import net.querz.nbt.mca.MCAUtil;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
@ -99,7 +98,8 @@ public boolean isGenerated() {
@Override @Override
public BlockState getBlockState(Vector3i pos) { public BlockState getBlockState(Vector3i pos) {
int sectionY = MCAUtil.blockToChunk(pos.getY()); int sectionY = pos.getY() >> 4;
if (sectionY < 0 || sectionY >= this.sections.length) return BlockState.AIR;
Section section = this.sections[sectionY]; Section section = this.sections[sectionY];
if (section == null) return BlockState.AIR; if (section == null) return BlockState.AIR;
@ -110,8 +110,10 @@ public BlockState getBlockState(Vector3i pos) {
@Override @Override
public LightData getLightData(Vector3i pos) { public LightData getLightData(Vector3i pos) {
if (!hasLight) return LightData.SKY; if (!hasLight) return LightData.SKY;
int sectionY = MCAUtil.blockToChunk(pos.getY()); int sectionY = pos.getY() >> 4;
if (sectionY < 0 || sectionY >= this.sections.length)
return (pos.getY() < 0) ? LightData.ZERO : LightData.SKY;
Section section = this.sections[sectionY]; Section section = this.sections[sectionY];
if (section == null) return LightData.SKY; if (section == null) return LightData.SKY;
@ -124,7 +126,8 @@ public Biome getBiome(int x, int y, int z) {
x = x & 0xF; // Math.floorMod(pos.getX(), 16) x = x & 0xF; // Math.floorMod(pos.getX(), 16)
z = z & 0xF; z = z & 0xF;
int biomeIntIndex = z * 16 + x; int biomeIntIndex = z * 16 + x;
if (biomeIntIndex >= this.biomes.length) return Biome.DEFAULT;
return biomeIdMapper.get(biomes[biomeIntIndex]); return biomeIdMapper.get(biomes[biomeIntIndex]);
} }

View File

@ -31,7 +31,6 @@
import de.bluecolored.bluemap.core.world.BlockState; import de.bluecolored.bluemap.core.world.BlockState;
import de.bluecolored.bluemap.core.world.LightData; import de.bluecolored.bluemap.core.world.LightData;
import net.querz.nbt.*; import net.querz.nbt.*;
import net.querz.nbt.mca.MCAUtil;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
@ -99,7 +98,8 @@ public boolean isGenerated() {
@Override @Override
public BlockState getBlockState(Vector3i pos) { public BlockState getBlockState(Vector3i pos) {
int sectionY = MCAUtil.blockToChunk(pos.getY()); int sectionY = pos.getY() >> 4;
if (sectionY < 0 || sectionY >= this.sections.length) return BlockState.AIR;
Section section = this.sections[sectionY]; Section section = this.sections[sectionY];
if (section == null) return BlockState.AIR; if (section == null) return BlockState.AIR;
@ -110,8 +110,10 @@ public BlockState getBlockState(Vector3i pos) {
@Override @Override
public LightData getLightData(Vector3i pos) { public LightData getLightData(Vector3i pos) {
if (!hasLight) return LightData.SKY; if (!hasLight) return LightData.SKY;
int sectionY = MCAUtil.blockToChunk(pos.getY()); int sectionY = pos.getY() >> 4;
if (sectionY < 0 || sectionY >= this.sections.length)
return (pos.getY() < 0) ? LightData.ZERO : LightData.SKY;
Section section = this.sections[sectionY]; Section section = this.sections[sectionY];
if (section == null) return LightData.SKY; if (section == null) return LightData.SKY;
@ -125,7 +127,8 @@ public Biome getBiome(int x, int y, int z) {
z = (z & 0xF) / 4; z = (z & 0xF) / 4;
y = y / 4; y = y / 4;
int biomeIntIndex = y * 16 + z * 4 + x; int biomeIntIndex = y * 16 + z * 4 + x;
if (biomeIntIndex >= this.biomes.length) return Biome.DEFAULT;
return biomeIdMapper.get(biomes[biomeIntIndex]); return biomeIdMapper.get(biomes[biomeIntIndex]);
} }

View File

@ -31,7 +31,6 @@
import de.bluecolored.bluemap.core.world.BlockState; import de.bluecolored.bluemap.core.world.BlockState;
import de.bluecolored.bluemap.core.world.LightData; import de.bluecolored.bluemap.core.world.LightData;
import net.querz.nbt.*; import net.querz.nbt.*;
import net.querz.nbt.mca.MCAUtil;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
@ -43,7 +42,8 @@ public class ChunkAnvil116 extends MCAChunk {
private boolean isGenerated; private boolean isGenerated;
private boolean hasLight; private boolean hasLight;
private Section[] sections; private Map<Integer, Section> sections;
private int sectionMin, sectionMax;
private int[] biomes; private int[] biomes;
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@ -62,11 +62,20 @@ public ChunkAnvil116(CompoundTag chunkTag, boolean ignoreMissingLightData, Biome
isGenerated = !status.equals("empty"); isGenerated = !status.equals("empty");
} }
this.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? this.sections = new HashMap<>(); // Is using a has-map the fastest/best way for an int->Object mapping?
this.sectionMin = Integer.MAX_VALUE;
this.sectionMax = Integer.MIN_VALUE;
if (levelData.containsKey("Sections")) { if (levelData.containsKey("Sections")) {
for (CompoundTag sectionTag : ((ListTag<CompoundTag>) levelData.getListTag("Sections"))) { for (CompoundTag sectionTag : ((ListTag<CompoundTag>) levelData.getListTag("Sections"))) {
if (sectionTag.getListTag("Palette") == null) continue; // ignore empty sections
Section section = new Section(sectionTag); Section section = new Section(sectionTag);
if (section.getSectionY() >= 0 && section.getSectionY() < sections.length) sections[section.getSectionY()] = section; int y = section.getSectionY();
if (sectionMin > y) sectionMin = y;
if (sectionMax < y) sectionMax = y;
sections.put(y, section);
} }
} }
@ -83,12 +92,8 @@ else if (tag instanceof IntArrayTag) {
this.biomes = ((IntArrayTag) tag).getValue(); this.biomes = ((IntArrayTag) tag).getValue();
} }
if (biomes == null || biomes.length == 0) { if (biomes == null) {
this.biomes = new int[1024]; this.biomes = new int[0];
}
if (biomes.length < 1024) {
this.biomes = Arrays.copyOf(biomes, 1024);
} }
} }
@ -99,9 +104,9 @@ public boolean isGenerated() {
@Override @Override
public BlockState getBlockState(Vector3i pos) { public BlockState getBlockState(Vector3i pos) {
int sectionY = MCAUtil.blockToChunk(pos.getY()); int sectionY = pos.getY() >> 4;
Section section = this.sections[sectionY]; Section section = this.sections.get(sectionY);
if (section == null) return BlockState.AIR; if (section == null) return BlockState.AIR;
return section.getBlockState(pos); return section.getBlockState(pos);
@ -111,24 +116,40 @@ public BlockState getBlockState(Vector3i pos) {
public LightData getLightData(Vector3i pos) { public LightData getLightData(Vector3i pos) {
if (!hasLight) return LightData.SKY; if (!hasLight) return LightData.SKY;
int sectionY = MCAUtil.blockToChunk(pos.getY()); int sectionY = pos.getY() >> 4;
Section section = this.sections[sectionY]; Section section = this.sections.get(sectionY);
if (section == null) return LightData.SKY; if (section == null) return (sectionY < sectionMin) ? LightData.ZERO : LightData.SKY;
return section.getLightData(pos); return section.getLightData(pos);
} }
@Override @Override
public Biome getBiome(int x, int y, int z) { public Biome getBiome(int x, int y, int z) {
if (biomes.length < 16) return Biome.DEFAULT;
x = (x & 0xF) / 4; // Math.floorMod(pos.getX(), 16) x = (x & 0xF) / 4; // Math.floorMod(pos.getX(), 16)
z = (z & 0xF) / 4; z = (z & 0xF) / 4;
y = y / 4; y = y / 4;
int biomeIntIndex = y * 16 + z * 4 + x; 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 biomeIdMapper.get(biomes[biomeIntIndex]); return biomeIdMapper.get(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;
}
private static class Section { private static class Section {
private static final String AIR_ID = "minecraft:air"; private static final String AIR_ID = "minecraft:air";

View File

@ -57,6 +57,14 @@ public int getDataVersion() {
public abstract LightData getLightData(Vector3i pos); public abstract LightData getLightData(Vector3i pos);
public abstract Biome getBiome(int x, int y, int z); public abstract Biome getBiome(int x, int y, int z);
public int getMaxY(int x, int z) {
return 255;
}
public int getMinY(int x, int z) {
return 0;
}
public static MCAChunk create(MCAWorld world, CompoundTag chunkTag, boolean ignoreMissingLightData) throws IOException { public static MCAChunk create(MCAWorld world, CompoundTag chunkTag, boolean ignoreMissingLightData) throws IOException {
int version = chunkTag.getInt("DataVersion"); int version = chunkTag.getInt("DataVersion");

View File

@ -130,24 +130,11 @@ public BlockState getBlockState(Vector3i pos) {
@Override @Override
public Biome getBiome(int x, int y, int z) { public Biome getBiome(int x, int y, int z) {
if (y < getMinY()) { return getChunk(x >> 4, z >> 4).getBiome(x, y, z);
y = getMinY();
} else if (y > getMaxY()) {
y = getMaxY();
}
MCAChunk chunk = getChunk(x >> 4, z >> 4);
return chunk.getBiome(x, y, z);
} }
@Override @Override
public Block getBlock(Vector3i pos) { public Block getBlock(Vector3i pos) {
if (pos.getY() < getMinY()) {
return new Block(this, BlockState.AIR, LightData.ZERO, Biome.DEFAULT, BlockProperties.TRANSPARENT, pos);
} else if (pos.getY() > getMaxY()) {
return new Block(this, BlockState.AIR, LightData.SKY, Biome.DEFAULT, BlockProperties.TRANSPARENT, pos);
}
MCAChunk chunk = getChunk(blockToChunk(pos)); MCAChunk chunk = getChunk(blockToChunk(pos));
BlockState blockState = getExtendedBlockState(chunk, pos); BlockState blockState = getExtendedBlockState(chunk, pos);
LightData lightData = chunk.getLightData(pos); LightData lightData = chunk.getLightData(pos);
@ -226,13 +213,13 @@ public int getSeaLevel() {
} }
@Override @Override
public int getMinY() { public int getMinY(int x, int z) {
return 0; return getChunk(x >> 4, z >> 4).getMinY(x, z);
} }
@Override @Override
public int getMaxY() { public int getMaxY(int x, int z) {
return 255; return getChunk(x >> 4, z >> 4).getMaxY(x, z);
} }
@Override @Override

View File

@ -73,13 +73,13 @@ public Vector3i getSpawnPoint() {
} }
@Override @Override
public int getMaxY() { public int getMaxY(int x, int z) {
return world.getMaxY(); return world.getMaxY(x, z);
} }
@Override @Override
public int getMinY() { public int getMinY(int x, int z) {
return world.getMinY(); return world.getMinY(x, z);
} }
@Override @Override

View File

@ -49,9 +49,9 @@ public interface World {
Vector3i getSpawnPoint(); Vector3i getSpawnPoint();
int getMaxY(); int getMaxY(int x, int z);
int getMinY(); int getMinY(int x, int z);
Grid getChunkGrid(); Grid getChunkGrid();