Separate level data handling from the main LevelProvider

This commit is contained in:
Dylan K. Taylor 2018-10-04 18:45:02 +01:00
parent f23bba053b
commit 49e47edcf5
9 changed files with 612 additions and 468 deletions

View File

@ -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();
}

View File

@ -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;
}

View File

@ -0,0 +1,136 @@
<?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\level\format\io;
use pocketmine\math\Vector3;
interface LevelData{
/**
* Saves information about the level state, such as weather, time, etc.
*/
public function save() : void;
/**
* @return string
*/
public function getName() : string;
/**
* Returns the generator name
*
* @return string
*/
public function getGenerator() : string;
/**
* @return array
*/
public function getGeneratorOptions() : array;
/**
* @return int
*/
public function getSeed() : int;
/**
* @return int
*/
public function getTime() : int;
/**
* @param int
*/
public function setTime(int $value) : void;
/**
* @return Vector3
*/
public function getSpawn() : Vector3;
/**
* @param Vector3 $pos
*/
public function setSpawn(Vector3 $pos) : void;
/**
* Returns the world difficulty. This will be one of the Level constants.
* @return int
*/
public function getDifficulty() : int;
/**
* Sets the world difficulty.
*
* @param int $difficulty
*/
public function setDifficulty(int $difficulty) : void;
/**
* Returns the time in ticks to the next rain level change.
* @return int
*/
public function getRainTime() : int;
/**
* Sets the time in ticks to the next rain level change.
* @param int $ticks
*/
public function setRainTime(int $ticks) : void;
/**
* @return float 0.0 - 1.0
*/
public function getRainLevel() : float;
/**
* @param float $level 0.0 - 1.0
*/
public function setRainLevel(float $level) : void;
/**
* Returns the time in ticks to the next lightning level change.
* @return int
*/
public function getLightningTime() : int;
/**
* Sets the time in ticks to the next lightning level change.
* @param int $ticks
*/
public function setLightningTime(int $ticks) : void;
/**
* @return float 0.0 - 1.0
*/
public function getLightningLevel() : float;
/**
* @param float $level 0.0 - 1.0
*/
public function setLightningLevel(float $level) : void;
}

View File

@ -24,7 +24,6 @@ declare(strict_types=1);
namespace pocketmine\level\format\io;
use pocketmine\level\format\Chunk;
use pocketmine\math\Vector3;
interface LevelProvider{
@ -66,18 +65,6 @@ interface LevelProvider{
*/
public static function generate(string $path, string $name, int $seed, string $generator, array $options = []);
/**
* Returns the generator name
*
* @return string
*/
public function getGenerator() : string;
/**
* @return array
*/
public function getGeneratorOptions() : array;
/**
* Saves a chunk (usually to disk).
*
@ -98,101 +85,17 @@ interface LevelProvider{
*/
public function loadChunk(int $chunkX, int $chunkZ) : ?Chunk;
/**
* @return string
*/
public function getName() : string;
/**
* @return int
*/
public function getTime() : int;
/**
* @param int
*/
public function setTime(int $value);
/**
* @return int
*/
public function getSeed() : int;
/**
* @return Vector3
*/
public function getSpawn() : Vector3;
/**
* @param Vector3 $pos
*/
public function setSpawn(Vector3 $pos);
/**
* Returns the world difficulty. This will be one of the Level constants.
* @return int
*/
public function getDifficulty() : int;
/**
* Sets the world difficulty.
* @param int $difficulty
*/
public function setDifficulty(int $difficulty);
/**
* Returns the time in ticks to the next rain level change.
* @return int
*/
public function getRainTime() : int;
/**
* Sets the time in ticks to the next rain level change.
* @param int $ticks
*/
public function setRainTime(int $ticks) : void;
/**
* @return float 0.0 - 1.0
*/
public function getRainLevel() : float;
/**
* @param float $level 0.0 - 1.0
*/
public function setRainLevel(float $level) : void;
/**
* Returns the time in ticks to the next lightning level change.
* @return int
*/
public function getLightningTime() : int;
/**
* Sets the time in ticks to the next lightning level change.
* @param int $ticks
*/
public function setLightningTime(int $ticks) : void;
/**
* @return float 0.0 - 1.0
*/
public function getLightningLevel() : float;
/**
* @param float $level 0.0 - 1.0
*/
public function setLightningLevel(float $level) : void;
/**
* Performs garbage collection in the level provider, such as cleaning up regions in Region-based worlds.
*/
public function doGarbageCollection();
/**
* Saves information about the level state, such as weather, time, etc.
* Returns information about the world
*
* @return LevelData
*/
public function saveLevelData();
public function getLevelData() : LevelData;
/**
* Performs cleanups necessary when the level provider is closed and no longer needed.

View File

@ -0,0 +1,100 @@
<?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\level\format\io\data;
use pocketmine\level\format\io\LevelData;
use pocketmine\level\LevelException;
use pocketmine\math\Vector3;
use pocketmine\nbt\tag\CompoundTag;
abstract class BaseNbtLevelData implements LevelData{
/** @var string */
protected $dataPath;
/** @var CompoundTag */
protected $compoundTag;
public function __construct(string $dataPath){
$this->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());
}
}

View File

@ -0,0 +1,193 @@
<?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\level\format\io\data;
use pocketmine\level\format\io\BaseLevelProvider;
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;
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\network\mcpe\protocol\ProtocolInfo;
use pocketmine\utils\Binary;
class BedrockLevelData extends BaseNbtLevelData{
public const CURRENT_STORAGE_VERSION = 6;
public const GENERATOR_LIMITED = 0;
public const GENERATOR_INFINITE = 1;
public const GENERATOR_FLAT = 2;
public static function generate(string $path, string $name, int $seed, string $generator, array $options = []){
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);
}
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);
}
}

View File

@ -0,0 +1,148 @@
<?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\level\format\io\data;
use pocketmine\level\format\io\BaseLevelProvider;
use pocketmine\level\generator\GeneratorManager;
use pocketmine\level\Level;
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;
class JavaLevelData extends BaseNbtLevelData{
public static function generate(string $path, string $name, int $seed, string $generator, array $options = [], int $version = 19133){
//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", $version),
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);
}
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
}
}

View File

@ -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);
}
/**

View File

@ -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(){