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

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