From 49e47edcf5f2bc3842369d8ac964bb11a83e8e2a Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Thu, 4 Oct 2018 18:45:02 +0100 Subject: [PATCH] Separate level data handling from the main LevelProvider --- src/pocketmine/level/Level.php | 24 +-- .../level/format/io/BaseLevelProvider.php | 43 +--- src/pocketmine/level/format/io/LevelData.php | 136 ++++++++++++ .../level/format/io/LevelProvider.php | 105 +-------- .../level/format/io/data/BaseNbtLevelData.php | 100 +++++++++ .../level/format/io/data/BedrockLevelData.php | 193 +++++++++++++++++ .../level/format/io/data/JavaLevelData.php | 148 +++++++++++++ .../level/format/io/leveldb/LevelDB.php | 199 +----------------- .../format/io/region/RegionLevelProvider.php | 132 +----------- 9 files changed, 612 insertions(+), 468 deletions(-) create mode 100644 src/pocketmine/level/format/io/LevelData.php create mode 100644 src/pocketmine/level/format/io/data/BaseNbtLevelData.php create mode 100644 src/pocketmine/level/format/io/data/BedrockLevelData.php create mode 100644 src/pocketmine/level/format/io/data/JavaLevelData.php diff --git a/src/pocketmine/level/Level.php b/src/pocketmine/level/Level.php index d71d7efb0..8a99561c1 100644 --- a/src/pocketmine/level/Level.php +++ b/src/pocketmine/level/Level.php @@ -333,11 +333,11 @@ class Level implements ChunkManager, Metadatable{ $this->provider = $provider; - $this->displayName = $this->provider->getName(); + $this->displayName = $this->provider->getLevelData()->getName(); $this->worldHeight = $this->provider->getWorldHeight(); $this->server->getLogger()->info($this->server->getLanguage()->translateString("pocketmine.level.preparing", [$this->displayName])); - $this->generator = GeneratorManager::getGenerator($this->provider->getGenerator()); + $this->generator = GeneratorManager::getGenerator($this->provider->getLevelData()->getGenerator()); $this->folderName = $name; @@ -346,7 +346,7 @@ class Level implements ChunkManager, Metadatable{ $this->neighbourBlockUpdateQueue = new \SplQueue(); - $this->time = $this->provider->getTime(); + $this->time = $this->provider->getLevelData()->getTime(); $this->chunkTickRadius = min($this->server->getViewDistance(), max(1, (int) $this->server->getProperty("chunk-ticking.tick-radius", 4))); $this->chunksPerTick = (int) $this->server->getProperty("chunk-ticking.per-tick", 40); @@ -389,7 +389,7 @@ class Level implements ChunkManager, Metadatable{ public function registerGeneratorToWorker(int $worker) : void{ $this->generatorRegisteredWorkers[$worker] = true; - $this->server->getAsyncPool()->submitTaskToWorker(new GeneratorRegisterTask($this, $this->generator, $this->provider->getGeneratorOptions()), $worker); + $this->server->getAsyncPool()->submitTaskToWorker(new GeneratorRegisterTask($this, $this->generator, $this->provider->getLevelData()->getGeneratorOptions()), $worker); } public function unregisterGenerator(){ @@ -1022,9 +1022,9 @@ class Level implements ChunkManager, Metadatable{ $this->server->getPluginManager()->callEvent(new LevelSaveEvent($this)); - $this->provider->setTime($this->time); + $this->provider->getLevelData()->setTime($this->time); $this->saveChunks(); - $this->provider->saveLevelData(); + $this->provider->getLevelData()->save(); return true; } @@ -2405,7 +2405,7 @@ class Level implements ChunkManager, Metadatable{ * @return Position */ public function getSpawnLocation() : Position{ - return Position::fromObject($this->provider->getSpawn(), $this); + return Position::fromObject($this->provider->getLevelData()->getSpawn(), $this); } /** @@ -2415,7 +2415,7 @@ class Level implements ChunkManager, Metadatable{ */ public function setSpawnLocation(Vector3 $pos){ $previousSpawn = $this->getSpawnLocation(); - $this->provider->setSpawn($pos); + $this->provider->getLevelData()->setSpawn($pos); $this->server->getPluginManager()->callEvent(new SpawnChangeEvent($this, $previousSpawn)); } @@ -2750,7 +2750,7 @@ class Level implements ChunkManager, Metadatable{ * @return bool */ public function isSpawnChunk(int $X, int $Z) : bool{ - $spawn = $this->provider->getSpawn(); + $spawn = $this->getSpawnLocation(); $spawnX = $spawn->x >> 4; $spawnZ = $spawn->z >> 4; @@ -2861,7 +2861,7 @@ class Level implements ChunkManager, Metadatable{ * @return int */ public function getSeed() : int{ - return $this->provider->getSeed(); + return $this->provider->getLevelData()->getSeed(); } public function getWorldHeight() : int{ @@ -2872,7 +2872,7 @@ class Level implements ChunkManager, Metadatable{ * @return int */ public function getDifficulty() : int{ - return $this->provider->getDifficulty(); + return $this->provider->getLevelData()->getDifficulty(); } /** @@ -2882,7 +2882,7 @@ class Level implements ChunkManager, Metadatable{ if($difficulty < 0 or $difficulty > 3){ throw new \InvalidArgumentException("Invalid difficulty level $difficulty"); } - $this->provider->setDifficulty($difficulty); + $this->provider->getLevelData()->setDifficulty($difficulty); $this->sendDifficulty(); } diff --git a/src/pocketmine/level/format/io/BaseLevelProvider.php b/src/pocketmine/level/format/io/BaseLevelProvider.php index c300b71fc..6c29574b3 100644 --- a/src/pocketmine/level/format/io/BaseLevelProvider.php +++ b/src/pocketmine/level/format/io/BaseLevelProvider.php @@ -25,13 +25,11 @@ namespace pocketmine\level\format\io; use pocketmine\level\format\Chunk; use pocketmine\level\LevelException; -use pocketmine\math\Vector3; -use pocketmine\nbt\tag\CompoundTag; abstract class BaseLevelProvider implements LevelProvider{ /** @var string */ protected $path; - /** @var CompoundTag */ + /** @var LevelData */ protected $levelData; public function __construct(string $path){ @@ -40,13 +38,10 @@ abstract class BaseLevelProvider implements LevelProvider{ } $this->path = $path; - $this->loadLevelData(); - $this->fixLevelData(); + $this->levelData = $this->loadLevelData(); } - abstract protected function loadLevelData() : void; - - abstract protected function fixLevelData() : void; + abstract protected function loadLevelData() : LevelData; /** * Hack to fix worlds broken previously by older versions of PocketMine-MP which incorrectly saved classpaths of @@ -66,7 +61,7 @@ abstract class BaseLevelProvider implements LevelProvider{ * * @return null|string Name of the correct generator to replace the broken value */ - protected static function hackyFixForGeneratorClasspathInLevelDat(string $className) : ?string{ + public static function hackyFixForGeneratorClasspathInLevelDat(string $className) : ?string{ //THESE ARE DELIBERATELY HARDCODED, DO NOT CHANGE! switch($className){ case 'pocketmine\level\generator\normal\Normal': @@ -82,36 +77,10 @@ abstract class BaseLevelProvider implements LevelProvider{ return $this->path; } - public function getName() : string{ - return $this->levelData->getString("LevelName"); - } - - public function getTime() : int{ - return $this->levelData->getLong("Time", 0, true); - } - - public function setTime(int $value){ - $this->levelData->setLong("Time", $value, true); //some older PM worlds had this in the wrong format - } - - public function getSeed() : int{ - return $this->levelData->getLong("RandomSeed"); - } - - public function getSpawn() : Vector3{ - return new Vector3($this->levelData->getInt("SpawnX"), $this->levelData->getInt("SpawnY"), $this->levelData->getInt("SpawnZ")); - } - - public function setSpawn(Vector3 $pos){ - $this->levelData->setInt("SpawnX", $pos->getFloorX()); - $this->levelData->setInt("SpawnY", $pos->getFloorY()); - $this->levelData->setInt("SpawnZ", $pos->getFloorZ()); - } - /** - * @return CompoundTag + * @return LevelData */ - public function getLevelData() : CompoundTag{ + public function getLevelData() : LevelData{ return $this->levelData; } diff --git a/src/pocketmine/level/format/io/LevelData.php b/src/pocketmine/level/format/io/LevelData.php new file mode 100644 index 000000000..ea08a2c0f --- /dev/null +++ b/src/pocketmine/level/format/io/LevelData.php @@ -0,0 +1,136 @@ +dataPath = $dataPath; + + if(!file_exists($this->dataPath)){ + throw new LevelException("Level data not found at $dataPath"); + } + + $this->compoundTag = $this->load(); + if($this->compoundTag === null){ + throw new LevelException("Invalid level data"); + } + } + + /** + * @return CompoundTag + */ + abstract protected function load() : ?CompoundTag; + + abstract protected function fix() : void; + + public function getCompoundTag() : CompoundTag{ + return $this->compoundTag; + } + + + /* The below are common between PC and PE */ + + public function getName() : string{ + return $this->compoundTag->getString("LevelName"); + } + + public function getGenerator() : string{ + return $this->compoundTag->getString("generatorName", "DEFAULT"); + } + + public function getGeneratorOptions() : array{ + return ["preset" => $this->compoundTag->getString("generatorOptions", "")]; + } + + public function getSeed() : int{ + return $this->compoundTag->getLong("RandomSeed"); + } + + public function getTime() : int{ + return $this->compoundTag->getLong("Time", 0, true); + } + + public function setTime(int $value) : void{ + $this->compoundTag->setLong("Time", $value, true); //some older PM worlds had this in the wrong format + } + + public function getSpawn() : Vector3{ + return new Vector3($this->compoundTag->getInt("SpawnX"), $this->compoundTag->getInt("SpawnY"), $this->compoundTag->getInt("SpawnZ")); + } + + public function setSpawn(Vector3 $pos) : void{ + $this->compoundTag->setInt("SpawnX", $pos->getFloorX()); + $this->compoundTag->setInt("SpawnY", $pos->getFloorY()); + $this->compoundTag->setInt("SpawnZ", $pos->getFloorZ()); + } + +} diff --git a/src/pocketmine/level/format/io/data/BedrockLevelData.php b/src/pocketmine/level/format/io/data/BedrockLevelData.php new file mode 100644 index 000000000..6fcf8c19f --- /dev/null +++ b/src/pocketmine/level/format/io/data/BedrockLevelData.php @@ -0,0 +1,193 @@ +write($levelData); + file_put_contents($path . "level.dat", Binary::writeLInt(self::CURRENT_STORAGE_VERSION) . Binary::writeLInt(strlen($buffer)) . $buffer); + } + + protected function load() : ?CompoundTag{ + $nbt = new LittleEndianNBTStream(); + $levelData = $nbt->read(substr(file_get_contents($this->dataPath), 8)); + if($levelData instanceof CompoundTag){ + $version = $levelData->getInt("StorageVersion", INT32_MAX, true); + if($version > self::CURRENT_STORAGE_VERSION){ + throw new LevelException("Specified LevelDB world format version ($version) is not supported by " . \pocketmine\NAME); + } + + return $levelData; + } + return null; + } + + protected function fix() : void{ + if(!$this->compoundTag->hasTag("generatorName", StringTag::class)){ + if($this->compoundTag->hasTag("Generator", IntTag::class)){ + switch($this->compoundTag->getInt("Generator")){ //Detect correct generator from MCPE data + case self::GENERATOR_FLAT: + $this->compoundTag->setString("generatorName", "flat"); + $this->compoundTag->setString("generatorOptions", "2;7,3,3,2;1"); + break; + case self::GENERATOR_INFINITE: + //TODO: add a null generator which does not generate missing chunks (to allow importing back to MCPE and generating more normal terrain without PocketMine messing things up) + $this->compoundTag->setString("generatorName", "default"); + $this->compoundTag->setString("generatorOptions", ""); + break; + case self::GENERATOR_LIMITED: + throw new LevelException("Limited worlds are not currently supported"); + default: + throw new LevelException("Unknown LevelDB world format type, this level cannot be loaded"); + } + }else{ + $this->compoundTag->setString("generatorName", "default"); + } + }elseif(($generatorName = BaseLevelProvider::hackyFixForGeneratorClasspathInLevelDat($this->compoundTag->getString("generatorName"))) !== null){ + $this->compoundTag->setString("generatorName", $generatorName); + } + + if(!$this->compoundTag->hasTag("generatorOptions", StringTag::class)){ + $this->compoundTag->setString("generatorOptions", ""); + } + } + + public function save() : void{ + $this->compoundTag->setInt("NetworkVersion", ProtocolInfo::CURRENT_PROTOCOL); + $this->compoundTag->setInt("StorageVersion", self::CURRENT_STORAGE_VERSION); + + $nbt = new LittleEndianNBTStream(); + $buffer = $nbt->write($this->compoundTag); + file_put_contents($this->dataPath, Binary::writeLInt(self::CURRENT_STORAGE_VERSION) . Binary::writeLInt(strlen($buffer)) . $buffer); + } + + public function getDifficulty() : int{ + return $this->compoundTag->getInt("Difficulty", Level::DIFFICULTY_NORMAL); + } + + public function setDifficulty(int $difficulty) : void{ + $this->compoundTag->setInt("Difficulty", $difficulty); //yes, this is intended! (in PE: int, PC: byte) + } + + public function getRainTime() : int{ + return $this->compoundTag->getInt("rainTime", 0); + } + + public function setRainTime(int $ticks) : void{ + $this->compoundTag->setInt("rainTime", $ticks); + } + + public function getRainLevel() : float{ + return $this->compoundTag->getFloat("rainLevel", 0.0); + } + + public function setRainLevel(float $level) : void{ + $this->compoundTag->setFloat("rainLevel", $level); + } + + public function getLightningTime() : int{ + return $this->compoundTag->getInt("lightningTime", 0); + } + + public function setLightningTime(int $ticks) : void{ + $this->compoundTag->setInt("lightningTime", $ticks); + } + + public function getLightningLevel() : float{ + return $this->compoundTag->getFloat("lightningLevel", 0.0); + } + + public function setLightningLevel(float $level) : void{ + $this->compoundTag->setFloat("lightningLevel", $level); + } +} diff --git a/src/pocketmine/level/format/io/data/JavaLevelData.php b/src/pocketmine/level/format/io/data/JavaLevelData.php new file mode 100644 index 000000000..8580c6478 --- /dev/null +++ b/src/pocketmine/level/format/io/data/JavaLevelData.php @@ -0,0 +1,148 @@ +writeCompressed(new CompoundTag("", [ + $levelData + ])); + file_put_contents($path . "level.dat", $buffer); + } + + protected function load() : ?CompoundTag{ + $nbt = new BigEndianNBTStream(); + $levelData = $nbt->readCompressed(file_get_contents($this->dataPath)); + if($levelData instanceof CompoundTag and $levelData->hasTag("Data", CompoundTag::class)){ + return $levelData->getCompoundTag("Data"); + } + return null; + } + + protected function fix() : void{ + if(!$this->compoundTag->hasTag("generatorName", StringTag::class)){ + $this->compoundTag->setString("generatorName", "default", true); + }elseif(($generatorName = BaseLevelProvider::hackyFixForGeneratorClasspathInLevelDat($this->compoundTag->getString("generatorName"))) !== null){ + $this->compoundTag->setString("generatorName", $generatorName); + } + + if(!$this->compoundTag->hasTag("generatorOptions", StringTag::class)){ + $this->compoundTag->setString("generatorOptions", ""); + } + } + + public function save() : void{ + $nbt = new BigEndianNBTStream(); + $this->compoundTag->setName("Data"); + $buffer = $nbt->writeCompressed(new CompoundTag("", [ + $this->compoundTag + ])); + file_put_contents($this->dataPath, $buffer); + } + + + public function getDifficulty() : int{ + return $this->compoundTag->getByte("Difficulty", Level::DIFFICULTY_NORMAL); + } + + public function setDifficulty(int $difficulty) : void{ + $this->compoundTag->setByte("Difficulty", $difficulty); + } + + public function getRainTime() : int{ + return $this->compoundTag->getInt("rainTime", 0); + } + + public function setRainTime(int $ticks) : void{ + $this->compoundTag->setInt("rainTime", $ticks); + } + + public function getRainLevel() : float{ + if($this->compoundTag->hasTag("rainLevel", FloatTag::class)){ //PocketMine/MCPE + return $this->compoundTag->getFloat("rainLevel"); + } + + return (float) $this->compoundTag->getByte("raining", 0); //PC vanilla + } + + public function setRainLevel(float $level) : void{ + $this->compoundTag->setFloat("rainLevel", $level); //PocketMine/MCPE + $this->compoundTag->setByte("raining", (int) ceil($level)); //PC vanilla + } + + public function getLightningTime() : int{ + return $this->compoundTag->getInt("thunderTime", 0); + } + + public function setLightningTime(int $ticks) : void{ + $this->compoundTag->setInt("thunderTime", $ticks); + } + + public function getLightningLevel() : float{ + if($this->compoundTag->hasTag("lightningLevel", FloatTag::class)){ //PocketMine/MCPE + return $this->compoundTag->getFloat("lightningLevel"); + } + + return (float) $this->compoundTag->getByte("thundering", 0); //PC vanilla + } + + public function setLightningLevel(float $level) : void{ + $this->compoundTag->setFloat("lightningLevel", $level); //PocketMine/MCPE + $this->compoundTag->setByte("thundering", (int) ceil($level)); //PC vanilla + } +} diff --git a/src/pocketmine/level/format/io/leveldb/LevelDB.php b/src/pocketmine/level/format/io/leveldb/LevelDB.php index 86625291f..f721f97b2 100644 --- a/src/pocketmine/level/format/io/leveldb/LevelDB.php +++ b/src/pocketmine/level/format/io/leveldb/LevelDB.php @@ -26,17 +26,14 @@ namespace pocketmine\level\format\io\leveldb; use pocketmine\level\format\Chunk; use pocketmine\level\format\io\BaseLevelProvider; use pocketmine\level\format\io\ChunkUtils; +use pocketmine\level\format\io\data\BedrockLevelData; use pocketmine\level\format\io\exception\UnsupportedChunkFormatException; +use pocketmine\level\format\io\LevelData; use pocketmine\level\format\SubChunk; -use pocketmine\level\generator\Flat; -use pocketmine\level\generator\GeneratorManager; -use pocketmine\level\Level; use pocketmine\level\LevelException; use pocketmine\nbt\LittleEndianNBTStream; -use pocketmine\nbt\tag\{ - ByteTag, CompoundTag, FloatTag, IntTag, LongTag, StringTag -}; -use pocketmine\network\mcpe\protocol\ProtocolInfo; +use pocketmine\nbt\tag\CompoundTag; +use pocketmine\nbt\tag\IntTag; use pocketmine\utils\Binary; use pocketmine\utils\BinaryStream; @@ -65,11 +62,6 @@ class LevelDB extends BaseLevelProvider{ public const ENTRY_FLAT_WORLD_LAYERS = "game_flatworldlayers"; - public const GENERATOR_LIMITED = 0; - public const GENERATOR_INFINITE = 1; - public const GENERATOR_FLAT = 2; - - public const CURRENT_STORAGE_VERSION = 6; //Current MCPE level format version public const CURRENT_LEVEL_CHUNK_VERSION = 7; public const CURRENT_LEVEL_SUBCHUNK_VERSION = 0; @@ -99,62 +91,8 @@ class LevelDB extends BaseLevelProvider{ $this->db = self::createDB($path); } - protected function loadLevelData() : void{ - $levelDatPath = $this->getPath() . "level.dat"; - if(!file_exists($levelDatPath)){ - throw new LevelException("level.dat not found"); - } - $nbt = new LittleEndianNBTStream(); - $levelData = $nbt->read(substr(file_get_contents($levelDatPath), 8)); - if($levelData instanceof CompoundTag){ - $this->levelData = $levelData; - }else{ - throw new LevelException("Invalid level.dat"); - } - - $version = $this->levelData->getInt("StorageVersion", INT32_MAX, true); - if($version > self::CURRENT_STORAGE_VERSION){ - throw new LevelException("Specified LevelDB world format version ($version) is not supported by " . \pocketmine\NAME); - } - } - - protected function fixLevelData() : void{ - $db = self::createDB($this->path); - - if(!$this->levelData->hasTag("generatorName", StringTag::class)){ - if($this->levelData->hasTag("Generator", IntTag::class)){ - switch($this->levelData->getInt("Generator")){ //Detect correct generator from MCPE data - case self::GENERATOR_FLAT: - $this->levelData->setString("generatorName", "flat"); - if(($layers = $db->get(self::ENTRY_FLAT_WORLD_LAYERS)) !== false){ //Detect existing custom flat layers - $layers = trim($layers, "[]"); - }else{ - $layers = "7,3,3,2"; - } - $this->levelData->setString("generatorOptions", "2;" . $layers . ";1"); - break; - case self::GENERATOR_INFINITE: - //TODO: add a null generator which does not generate missing chunks (to allow importing back to MCPE and generating more normal terrain without PocketMine messing things up) - $this->levelData->setString("generatorName", "default"); - $this->levelData->setString("generatorOptions", ""); - break; - case self::GENERATOR_LIMITED: - throw new LevelException("Limited worlds are not currently supported"); - default: - throw new LevelException("Unknown LevelDB world format type, this level cannot be loaded"); - } - }else{ - $this->levelData->setString("generatorName", "default"); - } - }elseif(($generatorName = self::hackyFixForGeneratorClasspathInLevelDat($this->levelData->getString("generatorName"))) !== null){ - $this->levelData->setString("generatorName", $generatorName); - } - - if(!$this->levelData->hasTag("generatorOptions", StringTag::class)){ - $this->levelData->setString("generatorOptions", ""); - } - - $db->close(); + protected function loadLevelData() : LevelData{ + return new BedrockLevelData($this->getPath() . "level.dat"); } public function getWorldHeight() : int{ @@ -172,130 +110,7 @@ class LevelDB extends BaseLevelProvider{ mkdir($path . "/db", 0777, true); } - switch($generator){ - case Flat::class: - $generatorType = self::GENERATOR_FLAT; - break; - default: - $generatorType = self::GENERATOR_INFINITE; - //TODO: add support for limited worlds - } - - $levelData = new CompoundTag("", [ - //Vanilla fields - new IntTag("DayCycleStopTime", -1), - new IntTag("Difficulty", Level::getDifficultyFromString((string) ($options["difficulty"] ?? "normal"))), - new ByteTag("ForceGameType", 0), - new IntTag("GameType", 0), - new IntTag("Generator", $generatorType), - new LongTag("LastPlayed", time()), - new StringTag("LevelName", $name), - new IntTag("NetworkVersion", ProtocolInfo::CURRENT_PROTOCOL), - //new IntTag("Platform", 2), //TODO: find out what the possible values are for - new LongTag("RandomSeed", $seed), - new IntTag("SpawnX", 0), - new IntTag("SpawnY", 32767), - new IntTag("SpawnZ", 0), - new IntTag("StorageVersion", self::CURRENT_STORAGE_VERSION), - new LongTag("Time", 0), - new ByteTag("eduLevel", 0), - new ByteTag("falldamage", 1), - new ByteTag("firedamage", 1), - new ByteTag("hasBeenLoadedInCreative", 1), //badly named, this actually determines whether achievements can be earned in this world... - new ByteTag("immutableWorld", 0), - new FloatTag("lightningLevel", 0.0), - new IntTag("lightningTime", 0), - new ByteTag("pvp", 1), - new FloatTag("rainLevel", 0.0), - new IntTag("rainTime", 0), - new ByteTag("spawnMobs", 1), - new ByteTag("texturePacksRequired", 0), //TODO - - //Additional PocketMine-MP fields - new CompoundTag("GameRules", []), - new ByteTag("hardcore", ($options["hardcore"] ?? false) === true ? 1 : 0), - new StringTag("generatorName", GeneratorManager::getGeneratorName($generator)), - new StringTag("generatorOptions", $options["preset"] ?? "") - ]); - - $nbt = new LittleEndianNBTStream(); - $buffer = $nbt->write($levelData); - file_put_contents($path . "level.dat", Binary::writeLInt(self::CURRENT_STORAGE_VERSION) . Binary::writeLInt(strlen($buffer)) . $buffer); - - - $db = self::createDB($path); - - if($generatorType === self::GENERATOR_FLAT and isset($options["preset"])){ - $layers = explode(";", $options["preset"])[1] ?? ""; - if($layers !== ""){ - $out = "["; - foreach(Flat::parseLayers($layers) as $result){ - $out .= $result[0] . ","; //only id, meta will unfortunately not survive :( - } - $out = rtrim($out, ",") . "]"; //remove trailing comma - $db->put(self::ENTRY_FLAT_WORLD_LAYERS, $out); //Add vanilla flatworld layers to allow terrain generation by MCPE to continue seamlessly - } - } - - $db->close(); - - } - - public function saveLevelData(){ - $this->levelData->setInt("NetworkVersion", ProtocolInfo::CURRENT_PROTOCOL); - $this->levelData->setInt("StorageVersion", self::CURRENT_STORAGE_VERSION); - - $nbt = new LittleEndianNBTStream(); - $buffer = $nbt->write($this->levelData); - file_put_contents($this->getPath() . "level.dat", Binary::writeLInt(self::CURRENT_STORAGE_VERSION) . Binary::writeLInt(strlen($buffer)) . $buffer); - } - - public function getGenerator() : string{ - return $this->levelData->getString("generatorName", ""); - } - - public function getGeneratorOptions() : array{ - return ["preset" => $this->levelData->getString("generatorOptions", "")]; - } - - public function getDifficulty() : int{ - return $this->levelData->getInt("Difficulty", Level::DIFFICULTY_NORMAL); - } - - public function setDifficulty(int $difficulty){ - $this->levelData->setInt("Difficulty", $difficulty); //yes, this is intended! (in PE: int, PC: byte) - } - - public function getRainTime() : int{ - return $this->levelData->getInt("rainTime", 0); - } - - public function setRainTime(int $ticks) : void{ - $this->levelData->setInt("rainTime", $ticks); - } - - public function getRainLevel() : float{ - return $this->levelData->getFloat("rainLevel", 0.0); - } - - public function setRainLevel(float $level) : void{ - $this->levelData->setFloat("rainLevel", $level); - } - - public function getLightningTime() : int{ - return $this->levelData->getInt("lightningTime", 0); - } - - public function setLightningTime(int $ticks) : void{ - $this->levelData->setInt("lightningTime", $ticks); - } - - public function getLightningLevel() : float{ - return $this->levelData->getFloat("lightningLevel", 0.0); - } - - public function setLightningLevel(float $level) : void{ - $this->levelData->setFloat("lightningLevel", $level); + BedrockLevelData::generate($path, $name, $seed, $generator, $options); } /** diff --git a/src/pocketmine/level/format/io/region/RegionLevelProvider.php b/src/pocketmine/level/format/io/region/RegionLevelProvider.php index 8b3701a7b..43751c069 100644 --- a/src/pocketmine/level/format/io/region/RegionLevelProvider.php +++ b/src/pocketmine/level/format/io/region/RegionLevelProvider.php @@ -25,16 +25,9 @@ namespace pocketmine\level\format\io\region; use pocketmine\level\format\Chunk; use pocketmine\level\format\io\BaseLevelProvider; -use pocketmine\level\generator\GeneratorManager; +use pocketmine\level\format\io\data\JavaLevelData; +use pocketmine\level\format\io\LevelData; use pocketmine\level\Level; -use pocketmine\level\LevelException; -use pocketmine\nbt\BigEndianNBTStream; -use pocketmine\nbt\tag\ByteTag; -use pocketmine\nbt\tag\CompoundTag; -use pocketmine\nbt\tag\FloatTag; -use pocketmine\nbt\tag\IntTag; -use pocketmine\nbt\tag\LongTag; -use pocketmine\nbt\tag\StringTag; use pocketmine\utils\MainLogger; abstract class RegionLevelProvider extends BaseLevelProvider{ @@ -79,128 +72,15 @@ abstract class RegionLevelProvider extends BaseLevelProvider{ if(!file_exists($path . "/region")){ mkdir($path . "/region", 0777); } - //TODO, add extra details - $levelData = new CompoundTag("Data", [ - new ByteTag("hardcore", ($options["hardcore"] ?? false) === true ? 1 : 0), - new ByteTag("Difficulty", Level::getDifficultyFromString((string) ($options["difficulty"] ?? "normal"))), - new ByteTag("initialized", 1), - new IntTag("GameType", 0), - new IntTag("generatorVersion", 1), //2 in MCPE - new IntTag("SpawnX", 256), - new IntTag("SpawnY", 70), - new IntTag("SpawnZ", 256), - new IntTag("version", static::getPcWorldFormatVersion()), - new IntTag("DayTime", 0), - new LongTag("LastPlayed", (int) (microtime(true) * 1000)), - new LongTag("RandomSeed", $seed), - new LongTag("SizeOnDisk", 0), - new LongTag("Time", 0), - new StringTag("generatorName", GeneratorManager::getGeneratorName($generator)), - new StringTag("generatorOptions", $options["preset"] ?? ""), - new StringTag("LevelName", $name), - new CompoundTag("GameRules", []) - ]); - $nbt = new BigEndianNBTStream(); - $buffer = $nbt->writeCompressed(new CompoundTag("", [ - $levelData - ])); - file_put_contents($path . "level.dat", $buffer); + + JavaLevelData::generate($path, $name, $seed, $generator, $options, static::getPcWorldFormatVersion()); } /** @var RegionLoader[] */ protected $regions = []; - protected function loadLevelData() : void{ - $levelDatPath = $this->getPath() . "level.dat"; - if(!file_exists($levelDatPath)){ - throw new LevelException("level.dat not found"); - } - $nbt = new BigEndianNBTStream(); - $levelData = $nbt->readCompressed(file_get_contents($levelDatPath)); - - if(!($levelData instanceof CompoundTag) or !$levelData->hasTag("Data", CompoundTag::class)){ - throw new LevelException("Invalid level.dat"); - } - - $this->levelData = $levelData->getCompoundTag("Data"); - } - - protected function fixLevelData() : void{ - if(!$this->levelData->hasTag("generatorName", StringTag::class)){ - $this->levelData->setString("generatorName", "default", true); - }elseif(($generatorName = self::hackyFixForGeneratorClasspathInLevelDat($this->levelData->getString("generatorName"))) !== null){ - $this->levelData->setString("generatorName", $generatorName); - } - - if(!$this->levelData->hasTag("generatorOptions", StringTag::class)){ - $this->levelData->setString("generatorOptions", ""); - } - } - - public function saveLevelData(){ - $nbt = new BigEndianNBTStream(); - $buffer = $nbt->writeCompressed(new CompoundTag("", [ - $this->levelData - ])); - file_put_contents($this->getPath() . "level.dat", $buffer); - } - - public function getGenerator() : string{ - return $this->levelData->getString("generatorName", "DEFAULT"); - } - - public function getGeneratorOptions() : array{ - return ["preset" => $this->levelData->getString("generatorOptions", "")]; - } - - public function getDifficulty() : int{ - return $this->levelData->getByte("Difficulty", Level::DIFFICULTY_NORMAL); - } - - public function setDifficulty(int $difficulty){ - $this->levelData->setByte("Difficulty", $difficulty); - } - - public function getRainTime() : int{ - return $this->levelData->getInt("rainTime", 0); - } - - public function setRainTime(int $ticks) : void{ - $this->levelData->setInt("rainTime", $ticks); - } - - public function getRainLevel() : float{ - if($this->levelData->hasTag("rainLevel", FloatTag::class)){ //PocketMine/MCPE - return $this->levelData->getFloat("rainLevel"); - } - - return (float) $this->levelData->getByte("raining", 0); //PC vanilla - } - - public function setRainLevel(float $level) : void{ - $this->levelData->setFloat("rainLevel", $level); //PocketMine/MCPE - $this->levelData->setByte("raining", (int) ceil($level)); //PC vanilla - } - - public function getLightningTime() : int{ - return $this->levelData->getInt("thunderTime", 0); - } - - public function setLightningTime(int $ticks) : void{ - $this->levelData->setInt("thunderTime", $ticks); - } - - public function getLightningLevel() : float{ - if($this->levelData->hasTag("lightningLevel", FloatTag::class)){ //PocketMine/MCPE - return $this->levelData->getFloat("lightningLevel"); - } - - return (float) $this->levelData->getByte("thundering", 0); //PC vanilla - } - - public function setLightningLevel(float $level) : void{ - $this->levelData->setFloat("lightningLevel", $level); //PocketMine/MCPE - $this->levelData->setByte("thundering", (int) ceil($level)); //PC vanilla + protected function loadLevelData() : LevelData{ + return new JavaLevelData($this->getPath() . "level.dat"); } public function doGarbageCollection(){