mirror of
https://github.com/pmmp/PocketMine-MP.git
synced 2025-04-21 00:07:30 +00:00
First look at loading 1.13+ worlds
This commit is contained in:
parent
dd3b79b142
commit
e58b3ba46c
@ -34,7 +34,7 @@
|
||||
"adhocore/json-comment": "^1.1",
|
||||
"fgrosse/phpasn1": "^2.3",
|
||||
"netresearch/jsonmapper": "^4.0",
|
||||
"pocketmine/bedrock-data": "~1.5.0+bedrock-1.18.0",
|
||||
"pocketmine/bedrock-data": "dev-experimental/upgrade-tables",
|
||||
"pocketmine/bedrock-protocol": "~7.3.0+bedrock-1.18.0",
|
||||
"pocketmine/binaryutils": "^0.2.1",
|
||||
"pocketmine/callback-validator": "^1.0.2",
|
||||
|
38
composer.lock
generated
38
composer.lock
generated
@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "9aa2f11ba68d00423732973554fafb20",
|
||||
"content-hash": "6723715c08c1582240f1c219df330f96",
|
||||
"packages": [
|
||||
{
|
||||
"name": "adhocore/json-comment",
|
||||
@ -249,16 +249,16 @@
|
||||
},
|
||||
{
|
||||
"name": "pocketmine/bedrock-data",
|
||||
"version": "1.5.0+bedrock-1.18.0",
|
||||
"version": "dev-experimental/upgrade-tables",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/pmmp/BedrockData.git",
|
||||
"reference": "482c679aa5ed0b81c088c2b1ff0b8110a94c8a6c"
|
||||
"reference": "747359be18b433659556c5adb527e47b0b150fdd"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/pmmp/BedrockData/zipball/482c679aa5ed0b81c088c2b1ff0b8110a94c8a6c",
|
||||
"reference": "482c679aa5ed0b81c088c2b1ff0b8110a94c8a6c",
|
||||
"url": "https://api.github.com/repos/pmmp/BedrockData/zipball/747359be18b433659556c5adb527e47b0b150fdd",
|
||||
"reference": "747359be18b433659556c5adb527e47b0b150fdd",
|
||||
"shasum": ""
|
||||
},
|
||||
"type": "library",
|
||||
@ -269,9 +269,9 @@
|
||||
"description": "Blobs of data generated from Minecraft: Bedrock Edition, used by PocketMine-MP",
|
||||
"support": {
|
||||
"issues": "https://github.com/pmmp/BedrockData/issues",
|
||||
"source": "https://github.com/pmmp/BedrockData/tree/bedrock-1.18.0"
|
||||
"source": "https://github.com/pmmp/BedrockData/tree/experimental/upgrade-tables"
|
||||
},
|
||||
"time": "2021-11-30T18:30:46+00:00"
|
||||
"time": "2022-02-05T16:34:56+00:00"
|
||||
},
|
||||
{
|
||||
"name": "pocketmine/bedrock-protocol",
|
||||
@ -1136,12 +1136,12 @@
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Symfony\\Polyfill\\Php80\\": ""
|
||||
},
|
||||
"files": [
|
||||
"bootstrap.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"Symfony\\Polyfill\\Php80\\": ""
|
||||
},
|
||||
"classmap": [
|
||||
"Resources/stubs"
|
||||
]
|
||||
@ -1219,12 +1219,12 @@
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Symfony\\Polyfill\\Php81\\": ""
|
||||
},
|
||||
"files": [
|
||||
"bootstrap.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"Symfony\\Polyfill\\Php81\\": ""
|
||||
},
|
||||
"classmap": [
|
||||
"Resources/stubs"
|
||||
]
|
||||
@ -1474,12 +1474,12 @@
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"DeepCopy\\": "src/DeepCopy/"
|
||||
},
|
||||
"files": [
|
||||
"src/DeepCopy/deep_copy.php"
|
||||
]
|
||||
],
|
||||
"psr-4": {
|
||||
"DeepCopy\\": "src/DeepCopy/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
@ -3507,7 +3507,9 @@
|
||||
],
|
||||
"aliases": [],
|
||||
"minimum-stability": "stable",
|
||||
"stability-flags": [],
|
||||
"stability-flags": {
|
||||
"pocketmine/bedrock-data": 20
|
||||
},
|
||||
"prefer-stable": false,
|
||||
"prefer-lowest": false,
|
||||
"platform": {
|
||||
|
@ -180,13 +180,13 @@ final class BlockStateUpgradeSchemaUtils{
|
||||
*
|
||||
* @return BlockStateUpgradeSchema[]
|
||||
*/
|
||||
public static function loadSchemas(string $path) : array{
|
||||
public static function loadSchemas(string $path, int $currentVersion) : array{
|
||||
$iterator = new \RegexIterator(
|
||||
new \FilesystemIterator(
|
||||
$path,
|
||||
\FilesystemIterator::CURRENT_AS_PATHNAME | \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::UNIX_PATHS
|
||||
),
|
||||
'/\/mapping_schema_(\d{4}).*\.json$/',
|
||||
'/\/(\d{4}).*\.json$/',
|
||||
\RegexIterator::GET_MATCH
|
||||
);
|
||||
|
||||
@ -209,7 +209,14 @@ final class BlockStateUpgradeSchemaUtils{
|
||||
}
|
||||
$model = $jsonMapper->map($json, new BlockStateUpgradeSchemaModel());
|
||||
|
||||
$result[$priority] = self::fromJsonModel($model);
|
||||
$schema = self::fromJsonModel($model);
|
||||
if($schema->getVersionId() > $currentVersion){
|
||||
//this might be a beta schema which shouldn't be applicable
|
||||
//TODO: why do we load the whole schema just to throw it away if it's too new? ...
|
||||
continue;
|
||||
}
|
||||
|
||||
$result[$priority] = $schema;
|
||||
}
|
||||
|
||||
ksort($result, SORT_NUMERIC);
|
||||
|
@ -34,6 +34,16 @@ final class BlockStateUpgrader{
|
||||
/** @var BlockStateUpgradeSchema[][] */
|
||||
private array $upgradeSchemas = [];
|
||||
|
||||
/**
|
||||
* @param BlockStateUpgradeSchema[] $upgradeSchemas
|
||||
* @phpstan-param array<int, BlockStateUpgradeSchema> $upgradeSchemas
|
||||
*/
|
||||
public function __construct(array $upgradeSchemas){
|
||||
foreach($upgradeSchemas as $priority => $schema){
|
||||
$this->addSchema($schema, $priority);
|
||||
}
|
||||
}
|
||||
|
||||
public function addSchema(BlockStateUpgradeSchema $schema, int $priority) : void{
|
||||
if(isset($this->upgradeSchemas[$schema->getVersionId()][$priority])){
|
||||
throw new \InvalidArgumentException("Another schema already has this priority");
|
||||
|
@ -27,12 +27,11 @@ use pocketmine\data\bedrock\blockstate\BlockStateData;
|
||||
use pocketmine\data\bedrock\blockstate\BlockStateSerializeException;
|
||||
use pocketmine\data\bedrock\blockstate\BlockStateSerializer;
|
||||
use pocketmine\data\bedrock\blockstate\BlockTypeNames;
|
||||
use pocketmine\data\bedrock\blockstate\CachingBlockStateSerializer;
|
||||
use pocketmine\data\bedrock\blockstate\convert\BlockObjectToBlockStateSerializer;
|
||||
use pocketmine\nbt\tag\CompoundTag;
|
||||
use pocketmine\utils\AssumptionFailedError;
|
||||
use pocketmine\utils\SingletonTrait;
|
||||
use pocketmine\utils\Utils;
|
||||
use pocketmine\world\format\io\GlobalBlockStateHandlers;
|
||||
use Webmozart\PathUtil\Path;
|
||||
use function file_get_contents;
|
||||
|
||||
@ -56,7 +55,7 @@ final class RuntimeBlockMapping{
|
||||
private function __construct(){
|
||||
$contents = Utils::assumeNotFalse(file_get_contents(Path::join(\pocketmine\BEDROCK_DATA_PATH, "canonical_block_states.nbt")), "Missing required resource file");
|
||||
$this->blockStateDictionary = BlockStateDictionary::loadFromString($contents);
|
||||
$this->blockStateSerializer = new CachingBlockStateSerializer(new BlockObjectToBlockStateSerializer());
|
||||
$this->blockStateSerializer = GlobalBlockStateHandlers::getSerializer();
|
||||
|
||||
$this->fallbackStateData = new BlockStateData(BlockTypeNames::INFO_UPDATE, CompoundTag::create(), BlockStateData::CURRENT_VERSION);
|
||||
}
|
||||
|
@ -23,8 +23,12 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\world\format\io;
|
||||
|
||||
use pocketmine\data\bedrock\blockstate\BlockStateData;
|
||||
use pocketmine\data\bedrock\blockstate\BlockTypeNames;
|
||||
use pocketmine\nbt\tag\CompoundTag;
|
||||
use pocketmine\world\format\io\exception\CorruptedWorldException;
|
||||
use pocketmine\world\format\io\exception\UnsupportedWorldFormatException;
|
||||
use pocketmine\world\format\PalettedBlockArray;
|
||||
use pocketmine\world\WorldException;
|
||||
use function file_exists;
|
||||
|
||||
@ -49,6 +53,44 @@ abstract class BaseWorldProvider implements WorldProvider{
|
||||
*/
|
||||
abstract protected function loadLevelData() : WorldData;
|
||||
|
||||
private function translatePalette(PalettedBlockArray $blockArray) : PalettedBlockArray{
|
||||
$palette = $blockArray->getPalette();
|
||||
|
||||
//TODO: this should be dependency-injected so it can be replaced, but that would break BC
|
||||
//also, we want it to be lazy-loaded ...
|
||||
$legacyBlockStateMapper = GlobalBlockStateHandlers::getLegacyBlockStateMapper();
|
||||
$blockStateDeserializer = GlobalBlockStateHandlers::getDeserializer();
|
||||
$newPalette = [];
|
||||
foreach($palette as $k => $legacyIdMeta){
|
||||
$newStateData = $legacyBlockStateMapper->fromIntIdMeta($legacyIdMeta >> 4, $legacyIdMeta & 0xf);
|
||||
if($newStateData === null){
|
||||
//TODO: remember data for unknown states so we can implement them later
|
||||
$newStateData = new BlockStateData(BlockTypeNames::INFO_UPDATE, CompoundTag::create(), BlockStateData::CURRENT_VERSION);
|
||||
}
|
||||
|
||||
$newPalette[$k] = $blockStateDeserializer->deserialize($newStateData);
|
||||
}
|
||||
|
||||
//TODO: this is sub-optimal since it reallocates the offset table multiple times
|
||||
return PalettedBlockArray::fromData(
|
||||
$blockArray->getBitsPerBlock(),
|
||||
$blockArray->getWordArray(),
|
||||
$newPalette
|
||||
);
|
||||
}
|
||||
|
||||
protected function palettizeLegacySubChunkXZY(string $idArray, string $metaArray) : PalettedBlockArray{
|
||||
return $this->translatePalette(SubChunkConverter::convertSubChunkXZY($idArray, $metaArray));
|
||||
}
|
||||
|
||||
protected function palettizeLegacySubChunkYZX(string $idArray, string $metaArray) : PalettedBlockArray{
|
||||
return $this->translatePalette(SubChunkConverter::convertSubChunkYZX($idArray, $metaArray));
|
||||
}
|
||||
|
||||
protected function palettizeLegacySubChunkFromColumn(string $idArray, string $metaArray, int $yOffset) : PalettedBlockArray{
|
||||
return $this->translatePalette(SubChunkConverter::convertSubChunkFromLegacyColumn($idArray, $metaArray, $yOffset));
|
||||
}
|
||||
|
||||
public function getPath() : string{
|
||||
return $this->path;
|
||||
}
|
||||
|
79
src/world/format/io/GlobalBlockStateHandlers.php
Normal file
79
src/world/format/io/GlobalBlockStateHandlers.php
Normal file
@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\world\format\io;
|
||||
|
||||
use pocketmine\data\bedrock\blockstate\BlockStateData;
|
||||
use pocketmine\data\bedrock\blockstate\BlockStateDeserializer;
|
||||
use pocketmine\data\bedrock\blockstate\BlockStateSerializer;
|
||||
use pocketmine\data\bedrock\blockstate\CachingBlockStateDeserializer;
|
||||
use pocketmine\data\bedrock\blockstate\CachingBlockStateSerializer;
|
||||
use pocketmine\data\bedrock\blockstate\convert\BlockObjectToBlockStateSerializer;
|
||||
use pocketmine\data\bedrock\blockstate\convert\BlockStateToBlockObjectDeserializer;
|
||||
use pocketmine\data\bedrock\blockstate\upgrade\BlockStateUpgrader;
|
||||
use pocketmine\data\bedrock\blockstate\upgrade\BlockStateUpgradeSchemaUtils;
|
||||
use pocketmine\data\bedrock\blockstate\upgrade\LegacyBlockStateMapper;
|
||||
use pocketmine\data\bedrock\blockstate\UpgradingBlockStateDeserializer;
|
||||
use pocketmine\data\bedrock\LegacyBlockIdToStringIdMap;
|
||||
use pocketmine\errorhandler\ErrorToExceptionHandler;
|
||||
use Webmozart\PathUtil\Path;
|
||||
use function file_get_contents;
|
||||
use const pocketmine\BEDROCK_DATA_PATH;
|
||||
|
||||
/**
|
||||
* Provides global access to blockstate serializers for all world providers.
|
||||
* TODO: Get rid of this. This is necessary to enable plugins to register custom serialize/deserialize handlers, and
|
||||
* also because we can't break BC of WorldProvider before PM5. While this is a sucky hack, it provides meaningful
|
||||
* benefits for now.
|
||||
*/
|
||||
final class GlobalBlockStateHandlers{
|
||||
|
||||
private static ?BlockStateSerializer $blockStateSerializer;
|
||||
|
||||
private static ?BlockStateDeserializer $blockStateDeserializer;
|
||||
|
||||
private static ?LegacyBlockStateMapper $legacyBlockStateMapper;
|
||||
|
||||
public static function getDeserializer() : BlockStateDeserializer{
|
||||
return self::$blockStateDeserializer ??= new CachingBlockStateDeserializer(
|
||||
new UpgradingBlockStateDeserializer(
|
||||
new BlockStateUpgrader(BlockStateUpgradeSchemaUtils::loadSchemas(
|
||||
Path::join(BEDROCK_DATA_PATH, 'upgrade_schema'),
|
||||
BlockStateData::CURRENT_VERSION
|
||||
)),
|
||||
new BlockStateToBlockObjectDeserializer()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public static function getSerializer() : BlockStateSerializer{
|
||||
return self::$blockStateSerializer ??= new CachingBlockStateSerializer(new BlockObjectToBlockStateSerializer());
|
||||
}
|
||||
|
||||
public static function getLegacyBlockStateMapper() : LegacyBlockStateMapper{
|
||||
return self::$legacyBlockStateMapper ??= LegacyBlockStateMapper::loadFromString(
|
||||
ErrorToExceptionHandler::trapAndRemoveFalse(fn() => file_get_contents(Path::join(BEDROCK_DATA_PATH, 'r12_to_current_block_map.bin'))),
|
||||
LegacyBlockIdToStringIdMap::getInstance()
|
||||
);
|
||||
}
|
||||
}
|
@ -26,7 +26,9 @@ namespace pocketmine\world\format\io\leveldb;
|
||||
use pocketmine\block\Block;
|
||||
use pocketmine\block\BlockLegacyIds;
|
||||
use pocketmine\data\bedrock\BiomeIds;
|
||||
use pocketmine\data\bedrock\LegacyBlockIdToStringIdMap;
|
||||
use pocketmine\data\bedrock\blockstate\BlockStateData;
|
||||
use pocketmine\data\bedrock\blockstate\BlockStateDeserializeException;
|
||||
use pocketmine\data\bedrock\blockstate\BlockTypeNames;
|
||||
use pocketmine\nbt\LittleEndianNbtSerializer;
|
||||
use pocketmine\nbt\NbtDataException;
|
||||
use pocketmine\nbt\NbtException;
|
||||
@ -44,7 +46,7 @@ use pocketmine\world\format\io\data\BedrockWorldData;
|
||||
use pocketmine\world\format\io\exception\CorruptedChunkException;
|
||||
use pocketmine\world\format\io\exception\CorruptedWorldException;
|
||||
use pocketmine\world\format\io\exception\UnsupportedWorldFormatException;
|
||||
use pocketmine\world\format\io\SubChunkConverter;
|
||||
use pocketmine\world\format\io\GlobalBlockStateHandlers;
|
||||
use pocketmine\world\format\io\WorldData;
|
||||
use pocketmine\world\format\io\WritableWorldProvider;
|
||||
use pocketmine\world\format\PalettedBlockArray;
|
||||
@ -70,32 +72,46 @@ use const LEVELDB_ZLIB_RAW_COMPRESSION;
|
||||
|
||||
class LevelDB extends BaseWorldProvider implements WritableWorldProvider{
|
||||
|
||||
//According to Tomasso, these aren't supposed to be readable anymore. Thankfully he didn't change the readable ones...
|
||||
/** @deprecated */
|
||||
protected const TAG_DATA_2D = ChunkDataKey::HEIGHTMAP_AND_2D_BIOMES;
|
||||
/** @deprecated */
|
||||
protected const TAG_DATA_2D_LEGACY = ChunkDataKey::HEIGHTMAP_AND_2D_BIOME_COLORS;
|
||||
/** @deprecated */
|
||||
protected const TAG_SUBCHUNK_PREFIX = ChunkDataKey::SUBCHUNK;
|
||||
/** @deprecated */
|
||||
protected const TAG_LEGACY_TERRAIN = ChunkDataKey::LEGACY_TERRAIN;
|
||||
/** @deprecated */
|
||||
protected const TAG_BLOCK_ENTITY = ChunkDataKey::BLOCK_ENTITIES;
|
||||
/** @deprecated */
|
||||
protected const TAG_ENTITY = ChunkDataKey::ENTITIES;
|
||||
/** @deprecated */
|
||||
protected const TAG_PENDING_TICK = ChunkDataKey::PENDING_SCHEDULED_TICKS;
|
||||
/** @deprecated */
|
||||
protected const TAG_BLOCK_EXTRA_DATA = ChunkDataKey::LEGACY_BLOCK_EXTRA_DATA;
|
||||
/** @deprecated */
|
||||
protected const TAG_BIOME_STATE = ChunkDataKey::BIOME_STATES;
|
||||
/** @deprecated */
|
||||
protected const TAG_STATE_FINALISATION = ChunkDataKey::FINALIZATION;
|
||||
|
||||
/** @deprecated */
|
||||
protected const TAG_BORDER_BLOCKS = ChunkDataKey::BORDER_BLOCKS;
|
||||
/** @deprecated */
|
||||
protected const TAG_HARDCODED_SPAWNERS = ChunkDataKey::HARDCODED_SPAWNERS;
|
||||
|
||||
protected const FINALISATION_NEEDS_INSTATICKING = 0;
|
||||
protected const FINALISATION_NEEDS_POPULATION = 1;
|
||||
protected const FINALISATION_DONE = 2;
|
||||
|
||||
protected const TAG_VERSION = ChunkDataKey::OLD_VERSION;
|
||||
/** @deprecated */
|
||||
protected const TAG_VERSION = ChunkDataKey::NEW_VERSION;
|
||||
|
||||
protected const ENTRY_FLAT_WORLD_LAYERS = "game_flatworldlayers";
|
||||
|
||||
protected const CURRENT_LEVEL_CHUNK_VERSION = ChunkVersion::v1_2_0; //yes, I know this is wrong, but it ensures vanilla auto-fixes stuff we currently don't
|
||||
protected const CURRENT_LEVEL_CHUNK_VERSION = ChunkVersion::v1_18_0_25_beta;
|
||||
protected const CURRENT_LEVEL_SUBCHUNK_VERSION = SubChunkVersion::PALETTED_MULTI;
|
||||
|
||||
private const CAVES_CLIFFS_EXPERIMENTAL_SUBCHUNK_KEY_OFFSET = 4;
|
||||
|
||||
/** @var \LevelDB */
|
||||
protected $db;
|
||||
|
||||
@ -171,18 +187,41 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{
|
||||
}
|
||||
$nbt = new LittleEndianNbtSerializer();
|
||||
$palette = [];
|
||||
$idMap = LegacyBlockIdToStringIdMap::getInstance();
|
||||
for($i = 0, $paletteSize = $stream->getLInt(); $i < $paletteSize; ++$i){
|
||||
|
||||
$paletteSize = $bitsPerBlock === 0 ? 1 : $stream->getLInt();
|
||||
|
||||
$blockStateDeserializer = GlobalBlockStateHandlers::getDeserializer();
|
||||
for($i = 0; $i < $paletteSize; ++$i){
|
||||
try{
|
||||
$offset = $stream->getOffset();
|
||||
|
||||
$tag = $nbt->read($stream->getBuffer(), $offset)->mustGetCompoundTag();
|
||||
$stream->setOffset($offset);
|
||||
|
||||
$id = $idMap->stringToLegacy($tag->getString("name")) ?? BlockLegacyIds::INFO_UPDATE;
|
||||
$data = $tag->getShort("val");
|
||||
$palette[] = ($id << Block::INTERNAL_METADATA_BITS) | $data;
|
||||
}catch(NbtException $e){
|
||||
if($tag->getTag("name") !== null && $tag->getTag("val") !== null){
|
||||
//Legacy (pre-1.13) blockstate - upgrade it to a version we understand
|
||||
$id = $tag->getString("name");
|
||||
$data = $tag->getShort("val");
|
||||
|
||||
$blockStateData = GlobalBlockStateHandlers::getLegacyBlockStateMapper()->fromStringIdMeta($id, $data);
|
||||
if($blockStateData === null){
|
||||
//TODO: this might be a slightly-invalid state that isn't in the mapping table
|
||||
$blockStateData = new BlockStateData(BlockTypeNames::INFO_UPDATE, CompoundTag::create(), BlockStateData::CURRENT_VERSION);
|
||||
}
|
||||
}else{
|
||||
//Modern (post-1.13) blockstate
|
||||
$blockStateData = BlockStateData::fromNbt($tag);
|
||||
}
|
||||
|
||||
try{
|
||||
$palette[] = $blockStateDeserializer->deserialize($blockStateData);
|
||||
}catch(BlockStateDeserializeException){
|
||||
//TODO: remember data for unknown states so we can implement them later
|
||||
//TODO: this is slow; we need to cache this
|
||||
//TODO: log this
|
||||
$palette[] = $blockStateDeserializer->deserialize(new BlockStateData(BlockTypeNames::INFO_UPDATE, CompoundTag::create(), BlockStateData::CURRENT_VERSION));
|
||||
}
|
||||
}catch(NbtException | BlockStateDeserializeException $e){
|
||||
throw new CorruptedChunkException("Invalid blockstate NBT at offset $i in paletted storage: " . $e->getMessage(), 0, $e);
|
||||
}
|
||||
}
|
||||
@ -215,6 +254,9 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{
|
||||
$extraDataLayers = [];
|
||||
$binaryStream = new BinaryStream($extraRawData);
|
||||
$count = $binaryStream->getLInt();
|
||||
|
||||
$legacyMapper = GlobalBlockStateHandlers::getLegacyBlockStateMapper();
|
||||
$blockStateDeserializer = GlobalBlockStateHandlers::getDeserializer();
|
||||
for($i = 0; $i < $count; ++$i){
|
||||
$key = $binaryStream->getLInt();
|
||||
$value = $binaryStream->getLShort();
|
||||
@ -226,23 +268,49 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{
|
||||
|
||||
$blockId = $value & 0xff;
|
||||
$blockData = ($value >> 8) & 0xf;
|
||||
$blockStateData = $legacyMapper->fromIntIdMeta($blockId, $blockData);
|
||||
if($blockStateData === null){
|
||||
//TODO: we could preserve this in case it's supported in the future, but this was historically only
|
||||
//used for grass anyway, so we probably don't need to care
|
||||
continue;
|
||||
}
|
||||
$blockStateId = $blockStateDeserializer->deserialize($blockStateData);
|
||||
|
||||
if(!isset($extraDataLayers[$ySub])){
|
||||
$extraDataLayers[$ySub] = new PalettedBlockArray(BlockLegacyIds::AIR << Block::INTERNAL_METADATA_BITS);
|
||||
}
|
||||
$extraDataLayers[$ySub]->set($x, $y, $z, ($blockId << Block::INTERNAL_METADATA_BITS) | $blockData);
|
||||
$extraDataLayers[$ySub]->set($x, $y, $z, $blockStateId);
|
||||
}
|
||||
|
||||
return $extraDataLayers;
|
||||
}
|
||||
|
||||
private function readVersion(int $chunkX, int $chunkZ) : ?int{
|
||||
$index = self::chunkIndex($chunkX, $chunkZ);
|
||||
$chunkVersionRaw = $this->db->get($index . ChunkDataKey::NEW_VERSION);
|
||||
if($chunkVersionRaw === false){
|
||||
$chunkVersionRaw = $this->db->get($index . ChunkDataKey::OLD_VERSION);
|
||||
if($chunkVersionRaw === false){
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return ord($chunkVersionRaw);
|
||||
}
|
||||
|
||||
private static function hasOffsetCavesAndCliffsSubChunks(int $chunkVersion) : bool{
|
||||
return $chunkVersion >= ChunkVersion::v1_16_220_50_unused && $chunkVersion <= ChunkVersion::v1_16_230_50_unused;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws CorruptedChunkException
|
||||
*/
|
||||
public function loadChunk(int $chunkX, int $chunkZ) : ?ChunkData{
|
||||
$index = LevelDB::chunkIndex($chunkX, $chunkZ);
|
||||
|
||||
$chunkVersionRaw = $this->db->get($index . ChunkDataKey::OLD_VERSION);
|
||||
if($chunkVersionRaw === false){
|
||||
$chunkVersion = $this->readVersion($chunkX, $chunkZ);
|
||||
if($chunkVersion === null){
|
||||
//TODO: this might be a slightly-corrupted chunk with a missing version field
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -252,10 +320,36 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{
|
||||
/** @var BiomeArray|null $biomeArray */
|
||||
$biomeArray = null;
|
||||
|
||||
$chunkVersion = ord($chunkVersionRaw);
|
||||
$hasBeenUpgraded = $chunkVersion < self::CURRENT_LEVEL_CHUNK_VERSION;
|
||||
|
||||
$subChunkKeyOffset = self::hasOffsetCavesAndCliffsSubChunks($chunkVersion) ? self::CAVES_CLIFFS_EXPERIMENTAL_SUBCHUNK_KEY_OFFSET : 0;
|
||||
|
||||
switch($chunkVersion){
|
||||
case ChunkVersion::v1_18_0_25_beta:
|
||||
case ChunkVersion::v1_18_0_24_unused:
|
||||
case ChunkVersion::v1_18_0_24_beta:
|
||||
case ChunkVersion::v1_18_0_22_unused:
|
||||
case ChunkVersion::v1_18_0_22_beta:
|
||||
case ChunkVersion::v1_18_0_20_unused:
|
||||
case ChunkVersion::v1_18_0_20_beta:
|
||||
case ChunkVersion::v1_17_40_unused:
|
||||
case ChunkVersion::v1_17_40_20_beta_experimental_caves_cliffs:
|
||||
case ChunkVersion::v1_17_30_25_unused:
|
||||
case ChunkVersion::v1_17_30_25_beta_experimental_caves_cliffs:
|
||||
case ChunkVersion::v1_17_30_23_unused:
|
||||
case ChunkVersion::v1_17_30_23_beta_experimental_caves_cliffs:
|
||||
case ChunkVersion::v1_16_230_50_unused:
|
||||
case ChunkVersion::v1_16_230_50_beta_experimental_caves_cliffs:
|
||||
case ChunkVersion::v1_16_220_50_unused:
|
||||
case ChunkVersion::v1_16_220_50_beta_experimental_caves_cliffs:
|
||||
case ChunkVersion::v1_16_210:
|
||||
case ChunkVersion::v1_16_100_57_beta:
|
||||
case ChunkVersion::v1_16_100_52_beta:
|
||||
case ChunkVersion::v1_16_0:
|
||||
case ChunkVersion::v1_16_0_51_beta:
|
||||
//TODO: check walls
|
||||
case ChunkVersion::v1_12_0_unused2:
|
||||
case ChunkVersion::v1_12_0_unused1:
|
||||
case ChunkVersion::v1_12_0_4_beta:
|
||||
case ChunkVersion::v1_11_1:
|
||||
case ChunkVersion::v1_11_0_4_beta:
|
||||
@ -266,13 +360,14 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{
|
||||
case ChunkVersion::v1_2_13:
|
||||
case ChunkVersion::v1_2_0:
|
||||
case ChunkVersion::v1_2_0_2_beta:
|
||||
case ChunkVersion::v1_1_0_converted_from_console:
|
||||
case ChunkVersion::v1_1_0:
|
||||
//TODO: check beds
|
||||
case ChunkVersion::v1_0_0:
|
||||
$convertedLegacyExtraData = $this->deserializeLegacyExtraData($index, $chunkVersion);
|
||||
|
||||
for($y = Chunk::MIN_SUBCHUNK_INDEX; $y <= Chunk::MAX_SUBCHUNK_INDEX; ++$y){
|
||||
if(($data = $this->db->get($index . ChunkDataKey::SUBCHUNK . chr($y))) === false){
|
||||
if(($data = $this->db->get($index . ChunkDataKey::SUBCHUNK . chr($y + $subChunkKeyOffset))) === false){
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -305,7 +400,7 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{
|
||||
throw new CorruptedChunkException($e->getMessage(), 0, $e);
|
||||
}
|
||||
|
||||
$storages = [SubChunkConverter::convertSubChunkXZY($blocks, $blockData)];
|
||||
$storages = [$this->palettizeLegacySubChunkXZY($blocks, $blockData)];
|
||||
if(isset($convertedLegacyExtraData[$y])){
|
||||
$storages[] = $convertedLegacyExtraData[$y];
|
||||
}
|
||||
@ -320,8 +415,13 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{
|
||||
$subChunks[$y] = new SubChunk(BlockLegacyIds::AIR << Block::INTERNAL_METADATA_BITS, $storages);
|
||||
break;
|
||||
case SubChunkVersion::PALETTED_MULTI:
|
||||
case SubChunkVersion::PALETTED_MULTI_WITH_OFFSET:
|
||||
//legacy extradata layers intentionally ignored because they aren't supposed to exist in v8
|
||||
$storageCount = $binaryStream->getByte();
|
||||
if($subChunkVersion >= SubChunkVersion::PALETTED_MULTI_WITH_OFFSET){
|
||||
//height ignored; this seems pointless since this is already in the key anyway
|
||||
$binaryStream->getByte();
|
||||
}
|
||||
if($storageCount > 0){
|
||||
$storages = [];
|
||||
|
||||
@ -367,7 +467,7 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{
|
||||
}
|
||||
|
||||
for($yy = 0; $yy < 8; ++$yy){
|
||||
$storages = [SubChunkConverter::convertSubChunkFromLegacyColumn($fullIds, $fullData, $yy)];
|
||||
$storages = [$this->palettizeLegacySubChunkFromColumn($fullIds, $fullData, $yy)];
|
||||
if(isset($convertedLegacyExtraData[$yy])){
|
||||
$storages[] = $convertedLegacyExtraData[$yy];
|
||||
}
|
||||
@ -434,15 +534,40 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{
|
||||
}
|
||||
|
||||
public function saveChunk(int $chunkX, int $chunkZ, ChunkData $chunkData) : void{
|
||||
$idMap = LegacyBlockIdToStringIdMap::getInstance();
|
||||
$index = LevelDB::chunkIndex($chunkX, $chunkZ);
|
||||
|
||||
$write = new \LevelDBWriteBatch();
|
||||
$write->put($index . ChunkDataKey::OLD_VERSION, chr(self::CURRENT_LEVEL_CHUNK_VERSION));
|
||||
|
||||
$previousVersion = $this->readVersion($chunkX, $chunkZ);
|
||||
$write->put($index . ChunkDataKey::NEW_VERSION, chr(self::CURRENT_LEVEL_CHUNK_VERSION));
|
||||
|
||||
$chunk = $chunkData->getChunk();
|
||||
|
||||
//TODO: This ensures that negative subchunks don't get destroyed in newer worlds for as long as we don't yet
|
||||
//support negative height. Since we don't save with a shift, if the old save had the subchunks shifted, we need
|
||||
//to shift them to their correct positions to avoid destroying data.
|
||||
//This can be removed once we support the full height.
|
||||
if($previousVersion !== null && self::hasOffsetCavesAndCliffsSubChunks($previousVersion)){
|
||||
$subChunks = $chunk->getSubChunks();
|
||||
|
||||
for($y = -4; $y <= 20; $y++){
|
||||
$key = $index . ChunkDataKey::SUBCHUNK . chr($y + self::CAVES_CLIFFS_EXPERIMENTAL_SUBCHUNK_KEY_OFFSET);
|
||||
if(
|
||||
(!isset($subChunks[$y]) || !$chunk->getTerrainDirtyFlag(Chunk::DIRTY_FLAG_BLOCKS)) &&
|
||||
($subChunkData = $this->db->get($key)) !== false
|
||||
){
|
||||
$write->delete($key);
|
||||
$write->put($index . ChunkDataKey::SUBCHUNK . chr($y & 0xff), $subChunkData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if($chunk->getTerrainDirtyFlag(Chunk::DIRTY_FLAG_BLOCKS)){
|
||||
$subChunks = $chunk->getSubChunks();
|
||||
|
||||
//TODO: this should not rely on globals, but in PM4 we have no other option, and it's not worse than what we
|
||||
//were doing before anyway ...
|
||||
$blockStateSerializer = GlobalBlockStateHandlers::getSerializer();
|
||||
foreach($subChunks as $y => $subChunk){
|
||||
$key = $index . ChunkDataKey::SUBCHUNK . chr($y);
|
||||
if($subChunk->isEmptyAuthoritative()){
|
||||
@ -454,24 +579,16 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{
|
||||
$layers = $subChunk->getBlockLayers();
|
||||
$subStream->putByte(count($layers));
|
||||
foreach($layers as $blocks){
|
||||
if($blocks->getBitsPerBlock() !== 0){
|
||||
$subStream->putByte($blocks->getBitsPerBlock() << 1);
|
||||
$subStream->put($blocks->getWordArray());
|
||||
}else{
|
||||
//TODO: we use these in-memory, but they aren't supported on disk by the game yet
|
||||
//polyfill them with a zero'd 1-bpb instead
|
||||
$subStream->putByte(1 << 1);
|
||||
$subStream->put(str_repeat("\x00", PalettedBlockArray::getExpectedWordArraySize(1)));
|
||||
}
|
||||
$subStream->putByte($blocks->getBitsPerBlock() << 1);
|
||||
$subStream->put($blocks->getWordArray());
|
||||
|
||||
$palette = $blocks->getPalette();
|
||||
$subStream->putLInt(count($palette));
|
||||
if($blocks->getBitsPerBlock() !== 0){
|
||||
$subStream->putLInt(count($palette));
|
||||
}
|
||||
$tags = [];
|
||||
foreach($palette as $p){
|
||||
$tags[] = new TreeRoot(CompoundTag::create()
|
||||
->setString("name", $idMap->legacyToString($p >> Block::INTERNAL_METADATA_BITS) ?? "minecraft:info_update")
|
||||
->setInt("oldid", $p >> Block::INTERNAL_METADATA_BITS) //PM only (debugging), vanilla doesn't have this
|
||||
->setShort("val", $p & Block::INTERNAL_METADATA_MASK));
|
||||
$tags[] = new TreeRoot($blockStateSerializer->serialize($p)->toNbt());
|
||||
}
|
||||
|
||||
$subStream->put((new LittleEndianNbtSerializer())->writeMultiple($tags));
|
||||
@ -528,7 +645,7 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{
|
||||
|
||||
public function getAllChunks(bool $skipCorrupted = false, ?\Logger $logger = null) : \Generator{
|
||||
foreach($this->db->getIterator() as $key => $_){
|
||||
if(strlen($key) === 9 && substr($key, -1) === ChunkDataKey::OLD_VERSION){
|
||||
if(strlen($key) === 9 && ($key[8] === ChunkDataKey::NEW_VERSION || $key[8] === ChunkDataKey::OLD_VERSION)){
|
||||
$chunkX = Binary::readLInt(substr($key, 0, 4));
|
||||
$chunkZ = Binary::readLInt(substr($key, 4, 4));
|
||||
try{
|
||||
@ -550,7 +667,7 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{
|
||||
public function calculateChunkCount() : int{
|
||||
$count = 0;
|
||||
foreach($this->db->getIterator() as $key => $_){
|
||||
if(strlen($key) === 9 && substr($key, -1) === ChunkDataKey::OLD_VERSION){
|
||||
if(strlen($key) === 9 && ($key[8] === ChunkDataKey::NEW_VERSION || $key[8] === ChunkDataKey::OLD_VERSION)){
|
||||
$count++;
|
||||
}
|
||||
}
|
||||
|
@ -26,14 +26,13 @@ namespace pocketmine\world\format\io\region;
|
||||
use pocketmine\block\Block;
|
||||
use pocketmine\block\BlockLegacyIds;
|
||||
use pocketmine\nbt\tag\CompoundTag;
|
||||
use pocketmine\world\format\io\SubChunkConverter;
|
||||
use pocketmine\world\format\SubChunk;
|
||||
|
||||
class Anvil extends RegionWorldProvider{
|
||||
use LegacyAnvilChunkTrait;
|
||||
|
||||
protected function deserializeSubChunk(CompoundTag $subChunk) : SubChunk{
|
||||
return new SubChunk(BlockLegacyIds::AIR << Block::INTERNAL_METADATA_BITS, [SubChunkConverter::convertSubChunkYZX(
|
||||
return new SubChunk(BlockLegacyIds::AIR << Block::INTERNAL_METADATA_BITS, [$this->palettizeLegacySubChunkYZX(
|
||||
self::readFixedSizeByteArray($subChunk, "Blocks", 4096),
|
||||
self::readFixedSizeByteArray($subChunk, "Data", 2048)
|
||||
)]);
|
||||
|
@ -38,7 +38,6 @@ use pocketmine\world\format\Chunk;
|
||||
use pocketmine\world\format\io\ChunkData;
|
||||
use pocketmine\world\format\io\ChunkUtils;
|
||||
use pocketmine\world\format\io\exception\CorruptedChunkException;
|
||||
use pocketmine\world\format\io\SubChunkConverter;
|
||||
use pocketmine\world\format\SubChunk;
|
||||
use function zlib_decode;
|
||||
|
||||
@ -75,7 +74,7 @@ class McRegion extends RegionWorldProvider{
|
||||
$fullData = self::readFixedSizeByteArray($chunk, "Data", 16384);
|
||||
|
||||
for($y = 0; $y < 8; ++$y){
|
||||
$subChunks[$y] = new SubChunk(BlockLegacyIds::AIR << Block::INTERNAL_METADATA_BITS, [SubChunkConverter::convertSubChunkFromLegacyColumn($fullIds, $fullData, $y)]);
|
||||
$subChunks[$y] = new SubChunk(BlockLegacyIds::AIR << Block::INTERNAL_METADATA_BITS, [$this->palettizeLegacySubChunkFromColumn($fullIds, $fullData, $y)]);
|
||||
}
|
||||
|
||||
$makeBiomeArray = function(string $biomeIds) : BiomeArray{
|
||||
|
@ -26,7 +26,6 @@ namespace pocketmine\world\format\io\region;
|
||||
use pocketmine\block\Block;
|
||||
use pocketmine\block\BlockLegacyIds;
|
||||
use pocketmine\nbt\tag\CompoundTag;
|
||||
use pocketmine\world\format\io\SubChunkConverter;
|
||||
use pocketmine\world\format\SubChunk;
|
||||
|
||||
/**
|
||||
@ -37,7 +36,7 @@ class PMAnvil extends RegionWorldProvider{
|
||||
use LegacyAnvilChunkTrait;
|
||||
|
||||
protected function deserializeSubChunk(CompoundTag $subChunk) : SubChunk{
|
||||
return new SubChunk(BlockLegacyIds::AIR << Block::INTERNAL_METADATA_BITS, [SubChunkConverter::convertSubChunkXZY(
|
||||
return new SubChunk(BlockLegacyIds::AIR << Block::INTERNAL_METADATA_BITS, [$this->palettizeLegacySubChunkXZY(
|
||||
self::readFixedSizeByteArray($subChunk, "Blocks", 4096),
|
||||
self::readFixedSizeByteArray($subChunk, "Data", 2048)
|
||||
)]);
|
||||
|
@ -44,7 +44,7 @@ class BlockStateUpgraderTest extends TestCase{
|
||||
private BlockStateUpgrader $upgrader;
|
||||
|
||||
public function setUp() : void{
|
||||
$this->upgrader = new BlockStateUpgrader();
|
||||
$this->upgrader = new BlockStateUpgrader([]);
|
||||
}
|
||||
|
||||
private function getNewSchema() : BlockStateUpgradeSchema{
|
||||
|
Loading…
x
Reference in New Issue
Block a user