diff --git a/src/Util.hpp b/src/Util.hpp index 1f50958..163813d 100644 --- a/src/Util.hpp +++ b/src/Util.hpp @@ -9,6 +9,7 @@ #include #include +#include #include #include @@ -29,13 +30,15 @@ static inline float clamp(float v, float min, float max) { // A block coordinate relative to a chunk/section (X/Y/Z) typedef uint8_t block_idx_t; // A section index in a chunk (Y) -typedef uint8_t section_idx_t; +typedef int8_t section_idx_t; // A chunk coordinate relative to a region (X/Z) typedef uint8_t chunk_idx_t; // A block coordinate relative to a region (X/Z) typedef uint16_t region_block_idx_t; // A block coordinate (Y) -typedef uint16_t y_idx_t; +typedef int16_t y_idx_t; + +const y_idx_t y_idx_min = std::numeric_limits::min(); } diff --git a/src/World/Chunk.cpp b/src/World/Chunk.cpp index 8b4b9eb..a447cda 100644 --- a/src/World/Chunk.cpp +++ b/src/World/Chunk.cpp @@ -18,27 +18,35 @@ namespace MinedMap { namespace World { Chunk::Chunk(const ChunkData *data) { - std::shared_ptr level = - assertValue(data->getRoot()->get("Level")); + std::shared_ptr sectionsTag; - std::shared_ptr sectionsTag = level->get("Sections"); - if (!sectionsTag || sectionsTag->empty()) - return; + auto level = data->getRoot()->get("Level"); + if (level) { + sectionsTag = level->get("Sections"); + if (!sectionsTag || sectionsTag->empty()) + return; - auto biomesIntArray = level->get("Biomes"); - auto biomesByteArray = level->get("Biomes"); + auto biomesIntArray = level->get("Biomes"); + auto biomesByteArray = level->get("Biomes"); - if (biomesIntArray && biomesIntArray->getLength() == BSIZE*BSIZE*BMAXY) { - biomeInts = std::move(biomesIntArray); - biomeFormat = INT_ARRAY; - } else if (biomesIntArray && biomesIntArray->getLength() == SIZE*SIZE) { - biomeInts = std::move(biomesIntArray); - biomeFormat = INT_ARRAY_PRE1_15; - } else if (biomesByteArray && biomesByteArray->getLength() == SIZE*SIZE) { - biomeBytes = std::move(biomesByteArray); - biomeFormat = BYTE_ARRAY; + if (biomesIntArray && biomesIntArray->getLength() == BSIZE*BSIZE*BMAXY) { + biomeInts = std::move(biomesIntArray); + biomeFormat = INT_ARRAY; + } else if (biomesIntArray && biomesIntArray->getLength() == SIZE*SIZE) { + biomeInts = std::move(biomesIntArray); + biomeFormat = INT_ARRAY_PRE1_15; + } else if (biomesByteArray && biomesByteArray->getLength() == SIZE*SIZE) { + biomeBytes = std::move(biomesByteArray); + biomeFormat = BYTE_ARRAY; + } else { + throw std::invalid_argument("corrupt biome data"); + } } else { - throw std::invalid_argument("corrupt biome data"); + sectionsTag = data->getRoot()->get("sections"); + if (!sectionsTag || sectionsTag->empty()) + return; + + biomeFormat = SECTION; } auto dataVersionTag = data->getRoot()->get("DataVersion"); @@ -48,25 +56,34 @@ Chunk::Chunk(const ChunkData *data) { auto s = std::dynamic_pointer_cast(sTag); std::unique_ptr
section = Section::makeSection(s, dataVersion); section_idx_t Y = section->getY(); + if (sections.empty()) + sectionOffset = Y; + + Y -= sectionOffset; + assertValue(Y >= 0 && size_t(Y) >= sections.size()); sections.resize(Y); sections.push_back(std::move(section)); } } uint8_t Chunk::getBiome(block_idx_t x, y_idx_t y, block_idx_t z) const { - if (x > SIZE || y > MAXY || z > SIZE) - throw std::invalid_argument("corrupt chunk data"); + if (x >= SIZE || z >= SIZE) + throw std::invalid_argument("getBiome: invalid block coordinate"); switch (biomeFormat) { case INT_ARRAY: + if (y < 0 || y >= MAXY) + break; return biomeInts->getValue((y>>BSHIFT)*BSIZE*BSIZE + (z>>BSHIFT)*BSIZE + (x>>BSHIFT)); case INT_ARRAY_PRE1_15: return biomeInts->getValue(z*SIZE + x); case BYTE_ARRAY: return biomeBytes->getValue(z*SIZE + x); default: - return 0xff; + break; } + + return 0xff; } Block Chunk::getBlock(block_idx_t x, Chunk::Height height, block_idx_t z) const { @@ -74,16 +91,19 @@ Block Chunk::getBlock(block_idx_t x, Chunk::Height height, block_idx_t z) const block.depth = height.depth; - section_idx_t Y = height.y >> HSHIFT; + if (height.y == y_idx_min) + return block; + + section_idx_t Y = (height.y >> HSHIFT) - sectionOffset; block_idx_t y = height.y & HMASK; - if (Y < sections.size() && sections[Y]) + if (Y >= 0 && size_t(Y) < sections.size() && sections[Y]) block.type = sections[Y]->getBlockStateAt(x, y, z); - section_idx_t Yt = (height.y + 1) >> HSHIFT; + section_idx_t Yt = ((height.y + 1) >> HSHIFT) - sectionOffset; block_idx_t yt = (height.y + 1) & HMASK; - if (Yt < sections.size() && sections[Yt]) + if (Yt >= 0 && size_t(Yt) < sections.size() && sections[Yt]) block.blockLight = sections[Yt]->getBlockLightAt(x, yt, z); return block; @@ -93,17 +113,17 @@ bool Chunk::getHeight( Chunk::Height *height, const Section *section, block_idx_t x, block_idx_t y, block_idx_t z, int flags ) const { - if (height->depth > 0) + if (height->depth > y_idx_min) return false; - if (!(flags & WITH_DEPTH) && height->y > 0) + if (!(flags & WITH_DEPTH) && height->y > y_idx_min) return false; const Resource::BlockType *type = section->getBlockStateAt(x, y, z); if (!type || !(type->flags & BLOCK_OPAQUE)) return false; - if (height->y == 0) + if (height->y == y_idx_min) height->y = (section->getY() << HSHIFT) + y; if (!(flags & WITH_DEPTH)) @@ -119,9 +139,14 @@ bool Chunk::getHeight( Chunk::Heightmap Chunk::getTopLayer(int flags) const { uint32_t done = 0; - Heightmap ret = {}; + Heightmap ret; - for (int16_t Y = sections.size() - 1; Y >= 0; Y--) { + for (block_idx_t z = 0; z < SIZE; z++) { + for (block_idx_t x = 0; x < SIZE; x++) + ret.v[x][z] = Height { .y = y_idx_min, .depth = y_idx_min }; + } + + for (section_idx_t Y = sections.size() - 1; Y >= 0; Y--) { if (done == SIZE*SIZE) break; diff --git a/src/World/Chunk.hpp b/src/World/Chunk.hpp index 1a59efe..34a5184 100644 --- a/src/World/Chunk.hpp +++ b/src/World/Chunk.hpp @@ -26,7 +26,7 @@ class Chunk { public: // Number of blocks in a chunk in x/z dimensions static const uint32_t SIZE = Section::SIZE; - // Maximum Y value + // Maximum Y value for pre-1.18 chunks static const y_idx_t MAXY = 256; // Shift to get from height to section index @@ -55,6 +55,7 @@ public: }; private: + section_idx_t sectionOffset; std::vector> sections; enum BiomeFormat { @@ -62,6 +63,7 @@ private: BYTE_ARRAY, INT_ARRAY_PRE1_15, INT_ARRAY, + SECTION, } biomeFormat = UNKNOWN; std::shared_ptr biomeBytes; @@ -73,9 +75,9 @@ private: ) const; const Resource::BlockType * getBlockStateAt(block_idx_t x, y_idx_t y, block_idx_t z) const { - section_idx_t Y = y >> HSHIFT; + section_idx_t Y = (y >> HSHIFT) - sectionOffset; - if (Y >= sections.size() || !sections[Y]) + if (Y < 0 || size_t(Y) >= sections.size() || !sections[Y]) return nullptr; return sections[Y]->getBlockStateAt(x, y & HMASK, z); diff --git a/src/World/Section.cpp b/src/World/Section.cpp index 948fee3..bc10f96 100644 --- a/src/World/Section.cpp +++ b/src/World/Section.cpp @@ -16,7 +16,7 @@ namespace MinedMap { namespace World { Section::Section(const std::shared_ptr §ion) { - Y = assertValue(section->get("Y"))->getValue(); + Y = int8_t(assertValue(section->get("Y"))->getValue()); blockLight = section->get("BlockLight"); } @@ -25,18 +25,32 @@ const Resource::BlockType * Section::getBlockStateAt(block_idx_t, block_idx_t, b } std::unique_ptr
Section::makeSection(const std::shared_ptr §ion, uint32_t dataVersion) { - std::shared_ptr blockStates = section->get("BlockStates"); - if (blockStates) { - const std::shared_ptr palette = assertValue(section->get("Palette")); + { + const std::shared_ptr blockStates = section->get("block_states"); + if (blockStates) { + const std::shared_ptr palette = assertValue(blockStates->get("palette")); + std::shared_ptr data = blockStates->get("data"); - return std::unique_ptr
(new PaletteSection(section, std::move(blockStates), palette, dataVersion)); + return std::unique_ptr
(new PaletteSection(section, std::move(data), palette, dataVersion)); + } } - std::shared_ptr blocks = section->get("Blocks"); - if (blocks) { - std::shared_ptr data = assertValue(section->get("Data")); + { + std::shared_ptr blockStates = section->get("BlockStates"); + if (blockStates) { + const std::shared_ptr palette = assertValue(section->get("Palette")); - return std::unique_ptr
(new LegacySection(section, std::move(blocks), std::move(data))); + return std::unique_ptr
(new PaletteSection(section, std::move(blockStates), palette, dataVersion)); + } + } + + { + std::shared_ptr blocks = section->get("Blocks"); + if (blocks) { + std::shared_ptr data = assertValue(section->get("Data")); + + return std::unique_ptr
(new LegacySection(section, std::move(blocks), std::move(data))); + } } return std::unique_ptr
(new Section(section)); @@ -81,7 +95,7 @@ PaletteSection::PaletteSection( expectedLength = (4096 + blocksPerWord - 1) / blocksPerWord; } - if (blockStates->getLength() != expectedLength) + if (blockStates && blockStates->getLength() != expectedLength) throw std::invalid_argument("corrupt section data"); for (const auto &entry : *paletteData) { @@ -97,6 +111,9 @@ PaletteSection::PaletteSection( } const Resource::BlockType * PaletteSection::getBlockStateAt(block_idx_t x, block_idx_t y, block_idx_t z) const { + if (!blockStates) + return palette.at(0); + size_t index = getIndex(x, y, z); size_t bitIndex; diff --git a/src/regiondump.cpp b/src/regiondump.cpp index a091536..62fe359 100644 --- a/src/regiondump.cpp +++ b/src/regiondump.cpp @@ -24,7 +24,7 @@ int main(int argc, char *argv[]) { } World::Region::visitChunks(argv[1], [&] (chunk_idx_t X, chunk_idx_t Z, const World::ChunkData *chunk) { - std::cout << "Chunk(" << X << ", " << Z << "): " + std::cout << "Chunk(" << unsigned(X) << ", " << unsigned(Z) << "): " << *chunk->getRoot() << std::endl; });