Added experimental LevelDB support, fixed a few issues with NBT, spawning and Binary R/W

This commit is contained in:
Shoghi Cervantes 2014-12-19 00:28:50 +01:00
parent db2dfc47a6
commit a605e90dfc
11 changed files with 705 additions and 19 deletions

View File

@ -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");

View File

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

View File

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

View 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)
);
}
}

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

View File

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

View File

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

View File

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

View File

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

View File

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

@ -1 +1 @@
Subproject commit 55cfe7cad845f133ac57349fdd60a901b7c7e5f6
Subproject commit 178d2a38f95d552fa5d91da26edc13a86d8054c6