From a605e90dfce1da16ded74a5aa500f4e83d5c2bbf Mon Sep 17 00:00:00 2001 From: Shoghi Cervantes Date: Fri, 19 Dec 2014 00:28:50 +0100 Subject: [PATCH] Added experimental LevelDB support, fixed a few issues with NBT, spawning and Binary R/W --- src/pocketmine/Server.php | 13 +- src/pocketmine/level/Level.php | 2 +- .../format/generic/BaseLevelProvider.php | 10 + src/pocketmine/level/format/leveldb/Chunk.php | 324 +++++++++++++++++ .../level/format/leveldb/LevelDB.php | 339 ++++++++++++++++++ .../level/format/mcregion/Chunk.php | 20 +- src/pocketmine/nbt/NBT.php | 6 + src/pocketmine/nbt/tag/Compound.php | 2 +- src/pocketmine/nbt/tag/Enum.php | 2 +- src/pocketmine/utils/Binary.php | 4 +- src/spl | 2 +- 11 files changed, 705 insertions(+), 19 deletions(-) create mode 100644 src/pocketmine/level/format/leveldb/Chunk.php create mode 100644 src/pocketmine/level/format/leveldb/LevelDB.php diff --git a/src/pocketmine/Server.php b/src/pocketmine/Server.php index f8ac37b5b..47f3587a7 100644 --- a/src/pocketmine/Server.php +++ b/src/pocketmine/Server.php @@ -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"); diff --git a/src/pocketmine/level/Level.php b/src/pocketmine/level/Level.php index 8b29fc824..444d896a5 100644 --- a/src/pocketmine/level/Level.php +++ b/src/pocketmine/level/Level.php @@ -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; } diff --git a/src/pocketmine/level/format/generic/BaseLevelProvider.php b/src/pocketmine/level/format/generic/BaseLevelProvider.php index 003a5f2b5..93b57086f 100644 --- a/src/pocketmine/level/format/generic/BaseLevelProvider.php +++ b/src/pocketmine/level/format/generic/BaseLevelProvider.php @@ -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(){ diff --git a/src/pocketmine/level/format/leveldb/Chunk.php b/src/pocketmine/level/format/leveldb/Chunk.php new file mode 100644 index 000000000..c77a5f681 --- /dev/null +++ b/src/pocketmine/level/format/leveldb/Chunk.php @@ -0,0 +1,324 @@ +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) + ); + } +} \ No newline at end of file diff --git a/src/pocketmine/level/format/leveldb/LevelDB.php b/src/pocketmine/level/format/leveldb/LevelDB.php new file mode 100644 index 000000000..d4f74cd1a --- /dev/null +++ b/src/pocketmine/level/format/leveldb/LevelDB.php @@ -0,0 +1,339 @@ +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; + } +} \ No newline at end of file diff --git a/src/pocketmine/level/format/mcregion/Chunk.php b/src/pocketmine/level/format/mcregion/Chunk.php index 09d19b68e..b82ebb332 100644 --- a/src/pocketmine/level/format/mcregion/Chunk.php +++ b/src/pocketmine/level/format/mcregion/Chunk.php @@ -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)); diff --git a/src/pocketmine/nbt/NBT.php b/src/pocketmine/nbt/NBT.php index 927148726..09fc3a7d6 100644 --- a/src/pocketmine/nbt/NBT.php +++ b/src/pocketmine/nbt/NBT.php @@ -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 = ""; } diff --git a/src/pocketmine/nbt/tag/Compound.php b/src/pocketmine/nbt/tag/Compound.php index a7074e5f0..95d34f61b 100644 --- a/src/pocketmine/nbt/tag/Compound.php +++ b/src/pocketmine/nbt/tag/Compound.php @@ -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{ diff --git a/src/pocketmine/nbt/tag/Enum.php b/src/pocketmine/nbt/tag/Enum.php index 6b89b1d75..58901e85b 100644 --- a/src/pocketmine/nbt/tag/Enum.php +++ b/src/pocketmine/nbt/tag/Enum.php @@ -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{ diff --git a/src/pocketmine/utils/Binary.php b/src/pocketmine/utils/Binary.php index 395dca2b0..a948a4e43 100644 --- a/src/pocketmine/utils/Binary.php +++ b/src/pocketmine/utils/Binary.php @@ -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){ diff --git a/src/spl b/src/spl index 55cfe7cad..178d2a38f 160000 --- a/src/spl +++ b/src/spl @@ -1 +1 @@ -Subproject commit 55cfe7cad845f133ac57349fdd60a901b7c7e5f6 +Subproject commit 178d2a38f95d552fa5d91da26edc13a86d8054c6