mirror of
https://github.com/pmmp/PocketMine-MP.git
synced 2025-04-20 16:00:20 +00:00
Added experimental LevelDB support, fixed a few issues with NBT, spawning and Binary R/W
This commit is contained in:
parent
db2dfc47a6
commit
a605e90dfc
@ -51,6 +51,7 @@ use pocketmine\inventory\InventoryType;
|
||||
use pocketmine\inventory\Recipe;
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\level\format\anvil\Anvil;
|
||||
use pocketmine\level\format\leveldb\LevelDB;
|
||||
use pocketmine\level\format\LevelProviderManager;
|
||||
use pocketmine\level\format\mcregion\McRegion;
|
||||
use pocketmine\level\generator\Flat;
|
||||
@ -991,7 +992,9 @@ class Server{
|
||||
$level = new Level($this, $name, $path, $provider);
|
||||
}catch(\Exception $e){
|
||||
$this->logger->error("Could not load level \"" . $name . "\": " . $e->getMessage());
|
||||
|
||||
if($this->logger instanceof MainLogger){
|
||||
$this->logger->logException($e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -1554,8 +1557,8 @@ class Server{
|
||||
|
||||
$this->addInterface($this->mainInterface = new RakLibInterface($this));
|
||||
|
||||
$this->logger->info("This server is running " . $this->getName() . " version " . ($version->isDev() ? TextFormat::YELLOW : "") . $version->get(true) . TextFormat::WHITE . " \"" . $this->getCodename() . "\" (API " . $this->getApiVersion() . ")", true, true, 0);
|
||||
$this->logger->info($this->getName() . " is distributed under the LGPL License", true, true, 0);
|
||||
$this->logger->info("This server is running " . $this->getName() . " version " . ($version->isDev() ? TextFormat::YELLOW : "") . $version->get(true) . TextFormat::WHITE . " \"" . $this->getCodename() . "\" (API " . $this->getApiVersion() . ")");
|
||||
$this->logger->info($this->getName() . " is distributed under the LGPL License");
|
||||
|
||||
PluginManager::$pluginParentTimer = new TimingsHandler("** Plugins");
|
||||
Timings::init();
|
||||
@ -1594,6 +1597,10 @@ class Server{
|
||||
|
||||
LevelProviderManager::addProvider($this, Anvil::class);
|
||||
LevelProviderManager::addProvider($this, McRegion::class);
|
||||
if(extension_loaded("leveldb")){
|
||||
$this->logger->debug("Enabling LevelDB support");
|
||||
LevelProviderManager::addProvider($this, LevelDB::class);
|
||||
}
|
||||
|
||||
|
||||
Generator::addGenerator(Flat::class, "flat");
|
||||
|
@ -2114,7 +2114,7 @@ class Level implements ChunkManager, Metadatable{
|
||||
$z = $v->z & 0x0f;
|
||||
if($chunk !== null){
|
||||
for(; $v->y > 0; --$v->y){
|
||||
if(Block::$solid[$chunk->getBlockId($x, $v->y & 0x7f, $z)]){
|
||||
if($v->y < 127 and Block::$solid[$chunk->getBlockId($x, $v->y & 0x7f, $z)]){
|
||||
$v->y++;
|
||||
break;
|
||||
}
|
||||
|
@ -22,11 +22,13 @@
|
||||
namespace pocketmine\level\format\generic;
|
||||
|
||||
use pocketmine\level\format\LevelProvider;
|
||||
use pocketmine\level\generator\Generator;
|
||||
use pocketmine\level\Level;
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\nbt\NBT;
|
||||
use pocketmine\nbt\tag\Compound;
|
||||
use pocketmine\nbt\tag\Int;
|
||||
use pocketmine\nbt\tag\String;
|
||||
use pocketmine\utils\LevelException;
|
||||
|
||||
abstract class BaseLevelProvider implements LevelProvider{
|
||||
@ -49,6 +51,14 @@ abstract class BaseLevelProvider implements LevelProvider{
|
||||
}else{
|
||||
throw new LevelException("Invalid level.dat");
|
||||
}
|
||||
|
||||
if(!isset($this->levelData->generatorName)){
|
||||
$this->levelData->generatorName = new String("generatorName", Generator::getGenerator("DEFAULT"));
|
||||
}
|
||||
|
||||
if(!isset($this->levelData->generatorOptions)){
|
||||
$this->levelData->generatorOptions = new String("generatorOptions", "");
|
||||
}
|
||||
}
|
||||
|
||||
public function getPath(){
|
||||
|
324
src/pocketmine/level/format/leveldb/Chunk.php
Normal file
324
src/pocketmine/level/format/leveldb/Chunk.php
Normal file
@ -0,0 +1,324 @@
|
||||
<?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/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
namespace pocketmine\level\format\leveldb;
|
||||
|
||||
use pocketmine\level\format\generic\BaseFullChunk;
|
||||
use pocketmine\level\format\LevelProvider;
|
||||
use pocketmine\nbt\NBT;
|
||||
use pocketmine\Player;
|
||||
use pocketmine\utils\Binary;
|
||||
|
||||
class Chunk extends BaseFullChunk{
|
||||
|
||||
protected $isPopulated = false;
|
||||
protected $isGenerated = false;
|
||||
|
||||
public function __construct($level, $chunkX, $chunkZ, $terrain, array $entityData = null, array $tileData = null){
|
||||
$heightMap = array_fill(0, 256, 127);
|
||||
|
||||
$offset = 0;
|
||||
|
||||
$blocks = substr($terrain, $offset, 32768);
|
||||
$offset += 32768;
|
||||
$data = substr($terrain, $offset, 16384);
|
||||
$offset += 16384;
|
||||
$skyLight = substr($terrain, $offset, 16384);
|
||||
$offset += 16384;
|
||||
$blockLight = substr($terrain, $offset, 16384);
|
||||
$offset += 16384;
|
||||
$biomes = substr($terrain, $offset, 256);
|
||||
$offset += 256;
|
||||
|
||||
$biomeColors = [];
|
||||
foreach(unpack("N*", substr($terrain, $offset, 1024)) as $c){
|
||||
$biomeColors[] = $c;
|
||||
}
|
||||
$offset += 1024;
|
||||
|
||||
parent::__construct($level, $chunkX, $chunkZ, $blocks, $data, $skyLight, $blockLight, $biomes, $biomeColors, $heightMap, $entityData === null ? [] : $entityData, $tileData === null ? [] : $tileData);
|
||||
}
|
||||
|
||||
public function getBlockId($x, $y, $z){
|
||||
return ord($this->blocks{($x << 11) | ($z << 7) | $y});
|
||||
}
|
||||
|
||||
public function setBlockId($x, $y, $z, $id){
|
||||
$this->blocks{($x << 11) | ($z << 7) | $y} = chr($id);
|
||||
$this->hasChanged = true;
|
||||
}
|
||||
|
||||
public function getBlockData($x, $y, $z){
|
||||
$m = ord($this->data{($x << 10) | ($z << 6) | ($y >> 1)});
|
||||
if(($y & 1) === 0){
|
||||
return $m & 0x0F;
|
||||
}else{
|
||||
return $m >> 4;
|
||||
}
|
||||
}
|
||||
|
||||
public function setBlockData($x, $y, $z, $data){
|
||||
$i = ($x << 10) | ($z << 6) | ($y >> 1);
|
||||
$old_m = ord($this->data{$i});
|
||||
if(($y & 1) === 0){
|
||||
$this->data{$i} = chr(($old_m & 0xf0) | ($data & 0x0f));
|
||||
}else{
|
||||
$this->data{$i} = chr((($data & 0x0f) << 4) | ($old_m & 0x0f));
|
||||
}
|
||||
$this->hasChanged = true;
|
||||
}
|
||||
|
||||
public function getFullBlock($x, $y, $z){
|
||||
$i = ($x << 11) | ($z << 7) | $y;
|
||||
if(($y & 1) === 0){
|
||||
return (ord($this->blocks{$i}) << 4) | (ord($this->data{$i >> 1}) & 0x0F);
|
||||
}else{
|
||||
return (ord($this->blocks{$i}) << 4) | (ord($this->data{$i >> 1}) >> 4);
|
||||
}
|
||||
}
|
||||
|
||||
public function getBlock($x, $y, $z, &$blockId, &$meta = null){
|
||||
$full = $this->getFullBlock($x, $y, $z);
|
||||
$blockId = $full >> 4;
|
||||
$meta = $full & 0x0f;
|
||||
}
|
||||
|
||||
public function setBlock($x, $y, $z, $blockId = null, $meta = null){
|
||||
$i = ($x << 11) | ($z << 7) | $y;
|
||||
|
||||
$changed = false;
|
||||
|
||||
if($blockId !== null){
|
||||
$blockId = chr($blockId);
|
||||
if($this->blocks{$i} !== $blockId){
|
||||
$this->blocks{$i} = $blockId;
|
||||
$changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if($meta !== null){
|
||||
$i >>= 1;
|
||||
$old_m = ord($this->data{$i});
|
||||
if(($y & 1) === 0){
|
||||
$this->data{$i} = chr(($old_m & 0xf0) | ($meta & 0x0f));
|
||||
if(($old_m & 0x0f) !== $meta){
|
||||
$changed = true;
|
||||
}
|
||||
}else{
|
||||
$this->data{$i} = chr((($meta & 0x0f) << 4) | ($old_m & 0x0f));
|
||||
if((($old_m & 0xf0) >> 4) !== $meta){
|
||||
$changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if($changed){
|
||||
$this->hasChanged = true;
|
||||
}
|
||||
|
||||
return $changed;
|
||||
}
|
||||
|
||||
public function getBlockSkyLight($x, $y, $z){
|
||||
$sl = ord($this->skyLight{($x << 10) | ($z << 6) | ($y >> 1)});
|
||||
if(($y & 1) === 0){
|
||||
return $sl & 0x0F;
|
||||
}else{
|
||||
return $sl >> 4;
|
||||
}
|
||||
}
|
||||
|
||||
public function setBlockSkyLight($x, $y, $z, $level){
|
||||
$i = ($x << 10) | ($z << 6) | ($y >> 1);
|
||||
$old_sl = ord($this->skyLight{$i});
|
||||
if(($y & 1) === 0){
|
||||
$this->skyLight{$i} = chr(($old_sl & 0xf0) | ($level & 0x0f));
|
||||
}else{
|
||||
$this->skyLight{$i} = chr((($level & 0x0f) << 4) | ($old_sl & 0x0f));
|
||||
}
|
||||
$this->hasChanged = true;
|
||||
}
|
||||
|
||||
public function getBlockLight($x, $y, $z){
|
||||
$l = ord($this->blockLight{($x << 10) | ($z << 6) | ($y >> 1)});
|
||||
if(($y & 1) === 0){
|
||||
return $l & 0x0F;
|
||||
}else{
|
||||
return $l >> 4;
|
||||
}
|
||||
}
|
||||
|
||||
public function setBlockLight($x, $y, $z, $level){
|
||||
$i = ($x << 10) | ($z << 6) | ($y >> 1);
|
||||
$old_l = ord($this->blockLight{$i});
|
||||
if(($y & 1) === 0){
|
||||
$this->blockLight{$i} = chr(($old_l & 0xf0) | ($level & 0x0f));
|
||||
}else{
|
||||
$this->blockLight{$i} = chr((($level & 0x0f) << 4) | ($old_l & 0x0f));
|
||||
}
|
||||
$this->hasChanged = true;
|
||||
}
|
||||
|
||||
public function getBlockIdColumn($x, $z){
|
||||
return substr($this->blocks, ($x << 11) + ($z << 7), 128);
|
||||
}
|
||||
|
||||
public function getBlockDataColumn($x, $z){
|
||||
return substr($this->data, ($x << 10) + ($z << 6), 64);
|
||||
}
|
||||
|
||||
public function getBlockSkyLightColumn($x, $z){
|
||||
return substr($this->skyLight, ($x << 10) + ($z << 6), 64);
|
||||
}
|
||||
|
||||
public function getBlockLightColumn($x, $z){
|
||||
return substr($this->blockLight, ($x << 10) + ($z << 6), 64);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isPopulated(){
|
||||
return $this->isPopulated;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $value
|
||||
*/
|
||||
public function setPopulated($value = 1){
|
||||
$this->isPopulated = (bool) $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isGenerated(){
|
||||
return $this->isGenerated;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $value
|
||||
*/
|
||||
public function setGenerated($value = 1){
|
||||
$this->isGenerated = (bool) $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $data
|
||||
* @param LevelProvider $provider
|
||||
*
|
||||
* @return Chunk
|
||||
*/
|
||||
public static function fromBinary($data, LevelProvider $provider = null){
|
||||
try{
|
||||
$chunkX = Binary::readLInt(substr($data, 0, 4));
|
||||
$chunkZ = Binary::readLInt(substr($data, 4, 4));
|
||||
$chunkData = substr($data, 8, -1);
|
||||
|
||||
$flags = ord(substr($data, -1));
|
||||
|
||||
$entities = null;
|
||||
$tiles = null;
|
||||
|
||||
if($provider instanceof LevelDB){
|
||||
$nbt = new NBT(NBT::LITTLE_ENDIAN);
|
||||
|
||||
$entityData = $provider->getDatabase()->get(substr($data, 0, 8) . "\x32");
|
||||
if($entityData !== false){
|
||||
$nbt->read($entityData);
|
||||
$entities = $nbt->getData();
|
||||
if(!is_array($entities)){
|
||||
$entities = [$entities];
|
||||
}
|
||||
}
|
||||
$tileData = $provider->getDatabase()->get(substr($data, 0, 8) . "\x31");
|
||||
if($tileData !== false){
|
||||
$nbt->read($tileData);
|
||||
$tiles = $nbt->getData();
|
||||
if(!is_array($tiles)){
|
||||
$tiles = [$tiles];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$chunk = new Chunk($provider instanceof LevelProvider ? $provider : LevelDB::class, $chunkX, $chunkZ, $chunkData, $entities, $tiles);
|
||||
if($flags & 0x01){
|
||||
$chunk->setGenerated();
|
||||
}
|
||||
if($flags & 0x02){
|
||||
$chunk->setPopulated();
|
||||
}
|
||||
return $chunk;
|
||||
}catch(\Exception $e){
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public function toBinary($saveExtra = false){
|
||||
$chunkIndex = LevelDB::chunkIndex($this->getX(), $this->getZ());
|
||||
|
||||
$provider = $this->getProvider();
|
||||
if($saveExtra and $provider instanceof LevelDB){
|
||||
$nbt = new NBT(NBT::LITTLE_ENDIAN);
|
||||
$entities = [];
|
||||
|
||||
foreach($this->getEntities() as $entity){
|
||||
if(!($entity instanceof Player) and !$entity->closed){
|
||||
$entity->saveNBT();
|
||||
$entities[] = $nbt->write($entity->namedtag);
|
||||
}
|
||||
}
|
||||
|
||||
if(count($entities) > 0){
|
||||
$provider->getDatabase()->put($chunkIndex . "\x32", implode($entities));
|
||||
}else{
|
||||
$provider->getDatabase()->delete($chunkIndex . "\x32");
|
||||
}
|
||||
|
||||
|
||||
$tiles = [];
|
||||
foreach($this->getTiles() as $tile){
|
||||
$tile->saveNBT();
|
||||
$tiles[] = $nbt->write($tile->namedtag);
|
||||
}
|
||||
|
||||
if(count($tiles) > 0){
|
||||
$provider->getDatabase()->put($chunkIndex . "\x31", implode($tiles));
|
||||
}else{
|
||||
$provider->getDatabase()->delete($chunkIndex . "\x31");
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
$biomeColors = pack("N*", ...$this->getBiomeColorArray());
|
||||
|
||||
return $chunkIndex .
|
||||
$this->getBlockIdArray() .
|
||||
$this->getBlockDataArray() .
|
||||
$this->getBlockSkyLightArray() .
|
||||
$this->getBlockLightArray() .
|
||||
$this->getBiomeIdArray() .
|
||||
$biomeColors . chr(
|
||||
($this->isPopulated() ? 0x02 : 0) | ($this->isGenerated() ? 0x01 : 0)
|
||||
);
|
||||
}
|
||||
}
|
339
src/pocketmine/level/format/leveldb/LevelDB.php
Normal file
339
src/pocketmine/level/format/leveldb/LevelDB.php
Normal file
@ -0,0 +1,339 @@
|
||||
<?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/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
namespace pocketmine\level\format\leveldb;
|
||||
|
||||
use pocketmine\level\format\FullChunk;
|
||||
use pocketmine\level\format\generic\BaseLevelProvider;
|
||||
use pocketmine\level\generator\Generator;
|
||||
use pocketmine\level\Level;
|
||||
use pocketmine\nbt\NBT;
|
||||
use pocketmine\nbt\tag\Byte;
|
||||
use pocketmine\nbt\tag\Compound;
|
||||
use pocketmine\nbt\tag\Int;
|
||||
use pocketmine\nbt\tag\Long;
|
||||
use pocketmine\nbt\tag\String;
|
||||
use pocketmine\tile\Spawnable;
|
||||
use pocketmine\utils\Binary;
|
||||
use pocketmine\utils\ChunkException;
|
||||
use pocketmine\utils\LevelException;
|
||||
|
||||
class LevelDB extends BaseLevelProvider{
|
||||
|
||||
/** @var Chunk[] */
|
||||
protected $chunks = [];
|
||||
|
||||
/** @var \LevelDB */
|
||||
protected $db;
|
||||
|
||||
public function __construct(Level $level, $path){
|
||||
$this->level = $level;
|
||||
$this->path = $path;
|
||||
@mkdir($this->path, 0777, true);
|
||||
$nbt = new NBT(NBT::LITTLE_ENDIAN);
|
||||
$nbt->read(substr(file_get_contents($this->getPath() . "level.dat"), 8));
|
||||
$levelData = $nbt->getData();
|
||||
if($levelData instanceof Compound){
|
||||
$this->levelData = $levelData;
|
||||
}else{
|
||||
throw new LevelException("Invalid level.dat");
|
||||
}
|
||||
|
||||
if(!isset($this->levelData->generatorName)){
|
||||
$this->levelData->generatorName = new String("generatorName", Generator::getGenerator("DEFAULT"));
|
||||
}
|
||||
|
||||
if(!isset($this->levelData->generatorOptions)){
|
||||
$this->levelData->generatorOptions = new String("generatorOptions", "");
|
||||
}
|
||||
|
||||
$this->db = new \LevelDB($this->path . "/db", [
|
||||
"compression" => LEVELDB_ZLIB_COMPRESSION
|
||||
]);
|
||||
}
|
||||
|
||||
public static function getProviderName(){
|
||||
return "leveldb";
|
||||
}
|
||||
|
||||
public static function getProviderOrder(){
|
||||
return self::ORDER_ZXY;
|
||||
}
|
||||
|
||||
public static function usesChunkSection(){
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function isValid($path){
|
||||
return file_exists($path . "/level.dat") and is_dir($path . "/db/");
|
||||
}
|
||||
|
||||
public static function generate($path, $name, $seed, $generator, array $options = []){
|
||||
@mkdir($path, 0777, true);
|
||||
@mkdir($path . "/db", 0777);
|
||||
//TODO, add extra details
|
||||
$levelData = new Compound(null, [
|
||||
"hardcore" => new Byte("hardcore", 0),
|
||||
"initialized" => new Byte("initialized", 1),
|
||||
"GameType" => new Int("GameType", 0),
|
||||
"generatorVersion" => new Int("generatorVersion", 1), //2 in MCPE
|
||||
"SpawnX" => new Int("SpawnX", 128),
|
||||
"SpawnY" => new Int("SpawnY", 70),
|
||||
"SpawnZ" => new Int("SpawnZ", 128),
|
||||
"version" => new Int("version", 19133),
|
||||
"DayTime" => new Int("DayTime", 0),
|
||||
"LastPlayed" => new Long("LastPlayed", microtime(true) * 1000),
|
||||
"RandomSeed" => new Long("RandomSeed", $seed),
|
||||
"SizeOnDisk" => new Long("SizeOnDisk", 0),
|
||||
"Time" => new Long("Time", 0),
|
||||
"generatorName" => new String("generatorName", Generator::getGeneratorName($generator)),
|
||||
"generatorOptions" => new String("generatorOptions", isset($options["preset"]) ? $options["preset"] : ""),
|
||||
"LevelName" => new String("LevelName", $name),
|
||||
"GameRules" => new Compound("GameRules", [])
|
||||
]);
|
||||
$nbt = new NBT(NBT::LITTLE_ENDIAN);
|
||||
$nbt->setData($levelData);
|
||||
$buffer = $nbt->write();
|
||||
file_put_contents($path . "level.dat", Binary::writeLInt(3) . Binary::writeLInt(strlen($buffer)) . $buffer);
|
||||
|
||||
$db = new \LevelDB($path . "/db");
|
||||
$db->close();
|
||||
}
|
||||
|
||||
public function saveLevelData(){
|
||||
$nbt = new NBT(NBT::LITTLE_ENDIAN);
|
||||
$nbt->setData($this->levelData);
|
||||
$buffer = $nbt->write();
|
||||
file_put_contents($this->getPath() . "level.dat", Binary::writeLInt(3) . Binary::writeLInt(strlen($buffer)) . $buffer);
|
||||
}
|
||||
|
||||
public function requestChunkTask($x, $z){
|
||||
$chunk = $this->getChunk($x, $z, false);
|
||||
if(!($chunk instanceof Chunk)){
|
||||
throw new ChunkException("Invalid Chunk sent");
|
||||
}
|
||||
|
||||
$tiles = "";
|
||||
$nbt = new NBT(NBT::LITTLE_ENDIAN);
|
||||
foreach($chunk->getTiles() as $tile){
|
||||
if($tile instanceof Spawnable){
|
||||
$nbt->setData($tile->getSpawnCompound());
|
||||
$tiles .= $nbt->write();
|
||||
}
|
||||
}
|
||||
|
||||
$biomeColors = pack("N*", ...$chunk->getBiomeColorArray());
|
||||
|
||||
$ordered = zlib_encode(
|
||||
Binary::writeLInt($x) . Binary::writeLInt($z) .
|
||||
$chunk->getBlockIdArray() .
|
||||
$chunk->getBlockDataArray() .
|
||||
$chunk->getBlockSkyLightArray() .
|
||||
$chunk->getBlockLightArray() .
|
||||
$chunk->getBiomeIdArray() .
|
||||
$biomeColors .
|
||||
$tiles
|
||||
, ZLIB_ENCODING_DEFLATE, Level::$COMPRESSION_LEVEL);
|
||||
|
||||
$this->getLevel()->chunkRequestCallback($x, $z, $ordered);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function unloadChunks(){
|
||||
foreach($this->chunks as $chunk){
|
||||
$this->unloadChunk($chunk->getX(), $chunk->getZ(), false);
|
||||
}
|
||||
$this->chunks = [];
|
||||
}
|
||||
|
||||
public function getGenerator(){
|
||||
return $this->levelData["generatorName"];
|
||||
}
|
||||
|
||||
public function getGeneratorOptions(){
|
||||
return ["preset" => $this->levelData["generatorOptions"]];
|
||||
}
|
||||
|
||||
public function getLoadedChunks(){
|
||||
return $this->chunks;
|
||||
}
|
||||
|
||||
public function isChunkLoaded($x, $z){
|
||||
return isset($this->chunks[Level::chunkHash($x, $z)]);
|
||||
}
|
||||
|
||||
public function saveChunks(){
|
||||
foreach($this->chunks as $chunk){
|
||||
$this->saveChunk($chunk->getX(), $chunk->getZ());
|
||||
}
|
||||
}
|
||||
|
||||
public function loadChunk($chunkX, $chunkZ, $create = false){
|
||||
if(isset($this->chunks[$index = Level::chunkHash($chunkX, $chunkZ)])){
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->level->timings->syncChunkLoadDataTimer->startTiming();
|
||||
$chunk = $this->readChunk($chunkX, $chunkZ, $create); //generate empty chunk if not loaded
|
||||
$this->level->timings->syncChunkLoadDataTimer->stopTiming();
|
||||
|
||||
if($chunk instanceof Chunk){
|
||||
$this->chunks[$index] = $chunk;
|
||||
return true;
|
||||
}else{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $chunkX
|
||||
* @param $chunkZ
|
||||
* @param bool $create
|
||||
*
|
||||
* @return Chunk
|
||||
*/
|
||||
private function readChunk($chunkX, $chunkZ, $create = false){
|
||||
$index = LevelDB::chunkIndex($chunkX, $chunkZ);
|
||||
|
||||
if(!$this->chunkExists($chunkX, $chunkZ) or ($data = $this->db->get($index . "\x30")) === false){
|
||||
return $create ? $this->generateChunk($chunkX, $chunkZ) : null;
|
||||
}
|
||||
|
||||
$flags = $this->db->get($index . "f");
|
||||
if($flags === false){
|
||||
$flags = "\x03";
|
||||
}
|
||||
|
||||
return Chunk::fromBinary($index . $data . $flags, $this);
|
||||
}
|
||||
|
||||
private function generateChunk($chunkX, $chunkZ){
|
||||
return new Chunk($this, $chunkX, $chunkZ, str_repeat("\x00", 32768) .
|
||||
str_repeat("\x00", 16384) . str_repeat("\xff", 16384) . str_repeat("\x00", 16384) .
|
||||
str_repeat("\x01", 256) .
|
||||
str_repeat("\x00\x85\xb2\x4a", 256));
|
||||
}
|
||||
|
||||
private function writeChunk(Chunk $chunk){
|
||||
$binary = $chunk->toBinary(true);
|
||||
$this->db->put($index = LevelDB::chunkIndex($chunk->getX(), $chunk->getZ()) . "\x30", substr($binary, 8, -1));
|
||||
$this->db->put($index . "f", substr($binary, -1));
|
||||
$this->db->put($index . "v", "\x02");
|
||||
}
|
||||
|
||||
public function unloadChunk($x, $z, $safe = true){
|
||||
$chunk = isset($this->chunks[$index = Level::chunkHash($x, $z)]) ? $this->chunks[$index] : null;
|
||||
if($chunk instanceof FullChunk and $chunk->unload(false, $safe)){
|
||||
unset($this->chunks[$index]);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function saveChunk($x, $z){
|
||||
if($this->isChunkLoaded($x, $z)){
|
||||
$this->writeChunk($this->getChunk($x, $z));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $chunkX
|
||||
* @param int $chunkZ
|
||||
* @param bool $create
|
||||
*
|
||||
* @return Chunk
|
||||
*/
|
||||
public function getChunk($chunkX, $chunkZ, $create = false){
|
||||
$index = Level::chunkHash($chunkX, $chunkZ);
|
||||
if(isset($this->chunks[$index])){
|
||||
return $this->chunks[$index];
|
||||
}else{
|
||||
$this->loadChunk($chunkX, $chunkZ, $create);
|
||||
|
||||
return isset($this->chunks[$index]) ? $this->chunks[$index] : null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \LevelDB
|
||||
*/
|
||||
public function getDatabase(){
|
||||
return $this->db;
|
||||
}
|
||||
|
||||
public function setChunk($chunkX, $chunkZ, FullChunk $chunk){
|
||||
if(!($chunk instanceof Chunk)){
|
||||
throw new ChunkException("Invalid Chunk class");
|
||||
}
|
||||
|
||||
$chunk->setProvider($this);
|
||||
|
||||
$chunk->setX($chunkX);
|
||||
$chunk->setZ($chunkZ);
|
||||
|
||||
if(isset($this->chunks[$index = Level::chunkHash($chunkX, $chunkZ)]) and $this->chunks[$index] !== $chunk){
|
||||
$this->unloadChunk($chunkX, $chunkZ, false);
|
||||
}
|
||||
|
||||
$this->chunks[$index] = $chunk;
|
||||
}
|
||||
|
||||
public static function createChunkSection($Y){
|
||||
return null;
|
||||
}
|
||||
|
||||
public static function chunkIndex($chunkX, $chunkZ){
|
||||
return Binary::writeLInt($chunkX) . Binary::writeLInt($chunkZ);
|
||||
}
|
||||
|
||||
private function chunkExists($chunkX, $chunkZ){
|
||||
return $this->db->get(LevelDB::chunkIndex($chunkX, $chunkZ) . "\x76") !== false;
|
||||
}
|
||||
|
||||
public function isChunkGenerated($chunkX, $chunkZ){
|
||||
if($this->chunkExists($chunkX, $chunkZ) and ($chunk = $this->getChunk($chunkX, $chunkZ, false)) !== null){
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function isChunkPopulated($chunkX, $chunkZ){
|
||||
$chunk = $this->getChunk($chunkX, $chunkZ);
|
||||
if($chunk instanceof FullChunk){
|
||||
return $chunk->isPopulated();
|
||||
}else{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function close(){
|
||||
$this->unloadChunks();
|
||||
$this->db->close();
|
||||
$this->level = null;
|
||||
}
|
||||
}
|
@ -92,16 +92,16 @@ class Chunk extends BaseFullChunk{
|
||||
}
|
||||
|
||||
public function getBlockId($x, $y, $z){
|
||||
return ord($this->blocks{($x << 11) + ($z << 7) + $y});
|
||||
return ord($this->blocks{($x << 11) | ($z << 7) | $y});
|
||||
}
|
||||
|
||||
public function setBlockId($x, $y, $z, $id){
|
||||
$this->blocks{($x << 11) + ($z << 7) + $y} = chr($id);
|
||||
$this->blocks{($x << 11) | ($z << 7) | $y} = chr($id);
|
||||
$this->hasChanged = true;
|
||||
}
|
||||
|
||||
public function getBlockData($x, $y, $z){
|
||||
$m = ord($this->data{($x << 10) + ($z << 6) + ($y >> 1)});
|
||||
$m = ord($this->data{($x << 10) | ($z << 6) | ($y >> 1)});
|
||||
if(($y & 1) === 0){
|
||||
return $m & 0x0F;
|
||||
}else{
|
||||
@ -110,7 +110,7 @@ class Chunk extends BaseFullChunk{
|
||||
}
|
||||
|
||||
public function setBlockData($x, $y, $z, $data){
|
||||
$i = ($x << 10) + ($z << 6) + ($y >> 1);
|
||||
$i = ($x << 10) | ($z << 6) | ($y >> 1);
|
||||
$old_m = ord($this->data{$i});
|
||||
if(($y & 1) === 0){
|
||||
$this->data{$i} = chr(($old_m & 0xf0) | ($data & 0x0f));
|
||||
@ -121,7 +121,7 @@ class Chunk extends BaseFullChunk{
|
||||
}
|
||||
|
||||
public function getFullBlock($x, $y, $z){
|
||||
$i = ($x << 11) + ($z << 7) + $y;
|
||||
$i = ($x << 11) | ($z << 7) | $y;
|
||||
if(($y & 1) === 0){
|
||||
return (ord($this->blocks{$i}) << 4) | (ord($this->data{$i >> 1}) & 0x0F);
|
||||
}else{
|
||||
@ -136,7 +136,7 @@ class Chunk extends BaseFullChunk{
|
||||
}
|
||||
|
||||
public function setBlock($x, $y, $z, $blockId = null, $meta = null){
|
||||
$i = ($x << 11) + ($z << 7) + $y;
|
||||
$i = ($x << 11) | ($z << 7) | $y;
|
||||
|
||||
$changed = false;
|
||||
|
||||
@ -172,7 +172,7 @@ class Chunk extends BaseFullChunk{
|
||||
}
|
||||
|
||||
public function getBlockSkyLight($x, $y, $z){
|
||||
$sl = ord($this->skyLight{($x << 10) + ($z << 6) + ($y >> 1)});
|
||||
$sl = ord($this->skyLight{($x << 10) | ($z << 6) | ($y >> 1)});
|
||||
if(($y & 1) === 0){
|
||||
return $sl & 0x0F;
|
||||
}else{
|
||||
@ -181,7 +181,7 @@ class Chunk extends BaseFullChunk{
|
||||
}
|
||||
|
||||
public function setBlockSkyLight($x, $y, $z, $level){
|
||||
$i = ($x << 10) + ($z << 6) + ($y >> 1);
|
||||
$i = ($x << 10) | ($z << 6) | ($y >> 1);
|
||||
$old_sl = ord($this->skyLight{$i});
|
||||
if(($y & 1) === 0){
|
||||
$this->skyLight{$i} = chr(($old_sl & 0xf0) | ($level & 0x0f));
|
||||
@ -192,7 +192,7 @@ class Chunk extends BaseFullChunk{
|
||||
}
|
||||
|
||||
public function getBlockLight($x, $y, $z){
|
||||
$l = ord($this->blockLight{($x << 10) + ($z << 6) + ($y >> 1)});
|
||||
$l = ord($this->blockLight{($x << 10) | ($z << 6) | ($y >> 1)});
|
||||
if(($y & 1) === 0){
|
||||
return $l & 0x0F;
|
||||
}else{
|
||||
@ -201,7 +201,7 @@ class Chunk extends BaseFullChunk{
|
||||
}
|
||||
|
||||
public function setBlockLight($x, $y, $z, $level){
|
||||
$i = ($x << 10) + ($z << 6) + ($y >> 1);
|
||||
$i = ($x << 10) | ($z << 6) | ($y >> 1);
|
||||
$old_l = ord($this->blockLight{$i});
|
||||
if(($y & 1) === 0){
|
||||
$this->blockLight{$i} = chr(($old_l & 0xf0) | ($level & 0x0f));
|
||||
|
@ -101,6 +101,12 @@ class NBT{
|
||||
$this->offset = 0;
|
||||
$this->buffer = $buffer;
|
||||
$this->data = $this->readTag();
|
||||
if($this->offset < strlen($this->buffer)){
|
||||
$this->data = [$this->data];
|
||||
do{
|
||||
$this->data[] = $this->readTag();
|
||||
}while($this->offset < strlen($this->buffer));
|
||||
}
|
||||
$this->buffer = "";
|
||||
}
|
||||
|
||||
|
@ -43,7 +43,7 @@ class Compound extends NamedTag implements \ArrayAccess{
|
||||
}
|
||||
|
||||
public function offsetGet($offset){
|
||||
if($this->{$offset} instanceof Tag){
|
||||
if(isset($this->{$offset}) and $this->{$offset} instanceof Tag){
|
||||
if($this->{$offset} instanceof \ArrayAccess){
|
||||
return $this->{$offset};
|
||||
}else{
|
||||
|
@ -53,7 +53,7 @@ class Enum extends NamedTag implements \ArrayAccess, \Countable{
|
||||
}
|
||||
|
||||
public function offsetGet($offset){
|
||||
if($this->{$offset} instanceof Tag){
|
||||
if(isset($this->{$offset}) and $this->{$offset} instanceof Tag){
|
||||
if($this->{$offset} instanceof \ArrayAccess){
|
||||
return $this->{$offset};
|
||||
}else{
|
||||
|
@ -385,8 +385,8 @@ class Binary{
|
||||
|
||||
public static function readLong($x){
|
||||
if(PHP_INT_SIZE === 8){
|
||||
list(, $int1, $int2) = unpack("N*", $x);
|
||||
return ($int1 << 32) | $int2;
|
||||
$int = unpack("N*", $x);
|
||||
return ($int[1] << 32) | $int[2];
|
||||
}else{
|
||||
$value = "0";
|
||||
for($i = 0; $i < 8; $i += 2){
|
||||
|
2
src/spl
2
src/spl
@ -1 +1 @@
|
||||
Subproject commit 55cfe7cad845f133ac57349fdd60a901b7c7e5f6
|
||||
Subproject commit 178d2a38f95d552fa5d91da26edc13a86d8054c6
|
Loading…
x
Reference in New Issue
Block a user