From e9311f5ceba5852ab7b34a7e3064cb0bee7237ec Mon Sep 17 00:00:00 2001 From: Shoghi Cervantes Date: Tue, 29 Jul 2014 18:43:52 +0200 Subject: [PATCH 01/13] Added base McRegion classes --- src/pocketmine/level/format/anvil/Chunk.php | 2 +- .../level/format/anvil/RegionLoader.php | 3 +- .../level/format/mcregion/Chunk.php | 163 ++++++++ .../level/format/mcregion/ChunkSection.php | 184 +++++++++ .../level/format/mcregion/ChunkSnapshot.php | 78 ++++ .../level/format/mcregion/McRegion.php | 285 +++++++++++++ .../level/format/mcregion/RegionLoader.php | 375 ++++++++++++++++++ 7 files changed, 1087 insertions(+), 3 deletions(-) create mode 100644 src/pocketmine/level/format/mcregion/Chunk.php create mode 100644 src/pocketmine/level/format/mcregion/ChunkSection.php create mode 100644 src/pocketmine/level/format/mcregion/ChunkSnapshot.php create mode 100644 src/pocketmine/level/format/mcregion/McRegion.php create mode 100644 src/pocketmine/level/format/mcregion/RegionLoader.php diff --git a/src/pocketmine/level/format/anvil/Chunk.php b/src/pocketmine/level/format/anvil/Chunk.php index 01d79f252..33a956171 100644 --- a/src/pocketmine/level/format/anvil/Chunk.php +++ b/src/pocketmine/level/format/anvil/Chunk.php @@ -73,7 +73,7 @@ class Chunk extends BaseChunk{ } if(!isset($this->nbt->BiomeColors) or !($this->nbt->BiomeColors instanceof IntArray)){ - $this->nbt->BiomeColors = new IntArray("BiomeColors", array_fill(0, 156, Binary::readInt("\x00\x85\xb2\x4a"))); + $this->nbt->BiomeColors = new IntArray("BiomeColors", array_fill(0, 256, Binary::readInt("\x00\x85\xb2\x4a"))); } $sections = []; diff --git a/src/pocketmine/level/format/anvil/RegionLoader.php b/src/pocketmine/level/format/anvil/RegionLoader.php index 20063ee89..0c7885eef 100644 --- a/src/pocketmine/level/format/anvil/RegionLoader.php +++ b/src/pocketmine/level/format/anvil/RegionLoader.php @@ -145,9 +145,8 @@ class RegionLoader{ $nbt->V = new Byte("V", self::VERSION); $nbt->InhabitedTime = new Long("InhabitedTime", 0); $nbt->Biomes = new ByteArray("Biomes", str_repeat(Binary::writeByte(-1), 256)); + $nbt->BiomeColors = new IntArray("BiomeColors", array_fill(0, 156, Binary::readInt("\x00\x85\xb2\x4a"))); $nbt->HeightMap = new IntArray("HeightMap", array_fill(0, 256, 127)); - //TODO: check type and name - //$nbt->GrassMap = new IntArray("GrassMap", array_fill(0, 256, 127)); $nbt->Sections = new Enum("Sections", []); $nbt->Sections->setTagType(NBT::TAG_Compound); $nbt->Entities = new Enum("Entities", []); diff --git a/src/pocketmine/level/format/mcregion/Chunk.php b/src/pocketmine/level/format/mcregion/Chunk.php new file mode 100644 index 000000000..b6f4cbd06 --- /dev/null +++ b/src/pocketmine/level/format/mcregion/Chunk.php @@ -0,0 +1,163 @@ +nbt = $nbt; + + if(isset($this->nbt->Entities) and $this->nbt->Entities instanceof Enum){ + $this->nbt->Entities->setTagType(NBT::TAG_Compound); + }else{ + $this->nbt->Entities = new Enum("Entities", []); + $this->nbt->Entities->setTagType(NBT::TAG_Compound); + } + + if(isset($this->nbt->TileEntities) and $this->nbt->TileEntities instanceof Enum){ + $this->nbt->TileEntities->setTagType(NBT::TAG_Compound); + }else{ + $this->nbt->TileEntities = new Enum("TileEntities", []); + $this->nbt->TileEntities->setTagType(NBT::TAG_Compound); + } + + if(isset($this->nbt->TileTicks) and $this->nbt->TileTicks instanceof Enum){ + $this->nbt->TileTicks->setTagType(NBT::TAG_Compound); + }else{ + $this->nbt->TileTicks = new Enum("TileTicks", []); + $this->nbt->TileTicks->setTagType(NBT::TAG_Compound); + } + + if(isset($this->nbt->Sections) and $this->nbt->Sections instanceof Enum){ + $this->nbt->Sections->setTagType(NBT::TAG_Compound); + }else{ + $this->nbt->Sections = new Enum("Sections", []); + $this->nbt->Sections->setTagType(NBT::TAG_Compound); + } + + if(!isset($this->nbt->Biomes) or !($this->nbt->Biomes instanceof ByteArray)){ + $this->nbt->Biomes = new ByteArray("Biomes", str_repeat("\x01", 256)); + } + + if(!isset($this->nbt->BiomeColors) or !($this->nbt->BiomeColors instanceof IntArray)){ + $this->nbt->BiomeColors = new IntArray("BiomeColors", array_fill(0, 156, Binary::readInt("\x00\x85\xb2\x4a"))); + } + + /** @var ChunkSection[] $sections */ + $sections = []; + + $blockLight = $skyLight = $datas = $blocks = [$fill = array_fill(0, 256, ""), $fill, $fill, $fill, $fill, $fill, $fill, $fill]; + + $offset = 0; + for($i = 0; $i < 256; ++$i){ + list($blocks[0][$i], $blocks[1][$i], $blocks[2][$i], $blocks[3][$i], $blocks[4][$i], $blocks[5][$i], $blocks[6][$i], $blocks[7][$i]) = str_split(substr($this->nbt->Blocks, $offset << 1, 128), 16); + list($datas[0][$i], $datas[1][$i], $datas[2][$i], $datas[3][$i], $datas[4][$i], $datas[5][$i], $datas[6][$i], $datas[7][$i]) = str_split(substr($this->nbt->Data, $offset, 64), 8); + list($skyLight[0][$i], $skyLight[1][$i], $skyLight[2][$i], $skyLight[3][$i], $skyLight[4][$i], $skyLight[5][$i], $skyLight[6][$i], $skyLight[7][$i]) = str_split(substr($this->nbt->SkyLight, $offset, 64), 8); + list($blockLight[0][$i], $blockLight[1][$i], $blockLight[2][$i], $blockLight[3][$i], $blockLight[4][$i], $blockLight[5][$i], $blockLight[6][$i], $blockLight[7][$i]) = str_split(substr($this->nbt->BlockLight, $offset, 64), 8); + $offset += 64; + } + + for($Y = 0; $Y < 8; ++$Y){ + $sections[$Y] = new ChunkSection( + $Y, + implode($blocks[$Y]), + implode($datas[$Y]), + implode($skyLight[$Y]), + implode($blockLight[$Y]) + ); + } + + for($y = 0; $y < 8; ++$y){ + if(substr_count($sections[$y]->getIdArray(), "\x00") === 4096){ + $sections[$y] = new EmptyChunkSection($y); + } + } + + parent::__construct($level, $this->nbt["xPos"], $this->nbt["zPos"], $sections, $this->nbt->Biomes->getValue(), $this->nbt->BiomeColors->getValue(), $this->nbt->Entities->getValue(), $this->nbt->TileEntities->getValue()); + } + + /** + * @return bool + */ + public function isPopulated(){ + return $this->nbt["TerrainPopulated"] > 0; + } + + /** + * @param int $value + */ + public function setPopulated($value = 1){ + $this->nbt->TerrainPopulated = new Byte("TerrainPopulated", $value); + } + + public function getChunkSnapshot($includeMaxBlockY = true, $includeBiome = false, $includeBiomeTemp = false){ + $blockId = ""; + $blockData = ""; + $blockSkyLight = ""; + $blockLight = ""; + $emptySections = [false, false, false, false, false, false, false, false]; + + $emptyBlocks = str_repeat("\x00", 4096); + $emptyHalf = str_repeat("\x00", 2048); + + foreach($this->sections as $i => $section){ + if($section instanceof EmptyChunkSection){ + $blockId .= $emptyBlocks; + $blockData .= $emptyHalf; + $blockSkyLight .= $emptyHalf; + $blockLight .= $emptyHalf; + $emptySections[$i] = true; + }else{ + $blockId .= $section->getIdArray(); + $blockData .= $section->getDataArray(); + $blockSkyLight .= $section->getSkyLightArray(); + $blockLight .= $section->getLightArray(); + } + } + + //TODO: maxBlockY, biomeMap, biomeTemp + + //TODO: time + return new ChunkSnapshot($this->getX(), $this->getZ(), $this->getLevel()->getName(), 0 /*$this->getLevel()->getTime()*/, $blockId, $blockData, $blockSkyLight, $blockLight, $emptySections, null, null, null, null); + } + + /** + * @return Compound + */ + public function getNBT(){ + return $this->nbt; + } +} \ No newline at end of file diff --git a/src/pocketmine/level/format/mcregion/ChunkSection.php b/src/pocketmine/level/format/mcregion/ChunkSection.php new file mode 100644 index 000000000..d15979738 --- /dev/null +++ b/src/pocketmine/level/format/mcregion/ChunkSection.php @@ -0,0 +1,184 @@ +y = $y; + $this->blocks = $blocks; + $this->data = $data; + $this->blockLight = $blockLight; + $this->skyLight = $skyLight; + } + + public function getY(){ + return $this->y; + } + + public function getBlockId($x, $y, $z){ + return ord($this->blocks{($z << 8) + ($x << 4) + $y}); + } + + public function setBlockId($x, $y, $z, $id){ + $this->blocks{($z << 8) + ($x << 4) + $y} = chr($id); + } + + public function getBlockData($x, $y, $z){ + $m = ord($this->data{($z << 7) + ($x << 3) + ($y >> 1)}); + if(($y & 1) === 0){ + return $m >> 4; + }else{ + return $m & 0x0F; + } + } + + public function setBlockData($x, $y, $z, $data){ + $i = ($z << 7) + ($x << 3) + ($y >> 1); + $old_m = ord($this->data{$i}); + if(($y & 1) === 0){ + $this->data{$i} = chr((($data & 0x0f) << 4) | ($old_m & 0x0f)); + }else{ + $this->data{$i} = chr(($old_m & 0xf0) | ($data & 0x0f)); + } + } + + public function getBlock($x, $y, $z, &$blockId, &$meta = null){ + $i = ($z << 8) + ($x << 4) + $y; + $blockId = ord($this->blocks{$i}); + $m = ord($this->data{$i >> 1}); + if(($y & 1) === 0){ + $meta = $m >> 4; + }else{ + $meta = $m & 0x0F; + } + } + + public function setBlock($x, $y, $z, $blockId = null, $meta = null){ + $i = ($z << 8) + ($x << 4) + $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((($meta & 0x0f) << 4) | ($old_m & 0x0f)); + if((($old_m & 0xf0) >> 4) !== $meta){ + $changed = true; + } + }else{ + $this->data{$i} = chr(($old_m & 0xf0) | ($meta & 0x0f)); + if(($old_m & 0x0f) !== $meta){ + $changed = true; + } + } + } + + return $changed; + } + + public function getBlockSkyLight($x, $y, $z){ + $sl = ord($this->skyLight{($z << 7) + ($x << 3) + ($y >> 1)}); + if(($y & 1) === 0){ + return $sl >> 4; + }else{ + return $sl & 0x0F; + } + } + + public function setBlockSkyLight($x, $y, $z, $level){ + $i = ($z << 7) + ($x << 3) + ($y >> 1); + $old_sl = ord($this->skyLight{$i}); + if(($y & 1) === 0){ + $this->skyLight{$i} = chr((($level & 0x0f) << 4) | ($old_sl & 0x0f)); + }else{ + $this->skyLight{$i} = chr(($old_sl & 0xf0) | ($level & 0x0f)); + } + } + + public function getBlockLight($x, $y, $z){ + $l = ord($this->blockLight{($z << 7) + ($x << 3) + ($y >> 1)}); + if(($y & 1) === 0){ + return $l >> 4; + }else{ + return $l & 0x0F; + } + } + + public function setBlockLight($x, $y, $z, $level){ + $i = ($z << 7) + ($x << 3) + ($y >> 1); + $old_l = ord($this->blockLight{$i}); + if(($y & 1) === 0){ + $this->blockLight{$i} = chr((($level & 0x0f) << 4) | ($old_l & 0x0f)); + }else{ + $this->blockLight{$i} = chr(($old_l & 0xf0) | ($level & 0x0f)); + } + } + + public function getBlockIdColumn($x, $z){ + return substr($this->blocks, ($z << 8) + ($x << 4), 16); + } + + public function getBlockDataColumn($x, $z){ + return substr($this->data, ($z << 7) + ($x << 3), 8); + } + + public function getBlockSkyLightColumn($x, $z){ + return substr($this->skyLight, ($z << 7) + ($x << 3), 8); + } + + public function getBlockLightColumn($x, $z){ + return substr($this->blockLight, ($z << 7) + ($x << 3), 8); + } + + public function getIdArray(){ + return $this->blocks; + } + + public function getDataArray(){ + return $this->data; + } + + public function getSkyLightArray(){ + return $this->skyLight; + } + + public function getLightArray(){ + return $this->blockLight; + } + +} \ No newline at end of file diff --git a/src/pocketmine/level/format/mcregion/ChunkSnapshot.php b/src/pocketmine/level/format/mcregion/ChunkSnapshot.php new file mode 100644 index 000000000..ce0e9f91e --- /dev/null +++ b/src/pocketmine/level/format/mcregion/ChunkSnapshot.php @@ -0,0 +1,78 @@ +blockId{ + (($y >> 4) << 12) //get section index + + ($y << 8) + ($z << 4) + $x //get block index in section + }); + } + + public function getBlockData($x, $y, $z){ + $data = ord($this->blockData{ + (($y >> 4) << 11) //get section index + + ($y << 7) + ($z << 3) + ($x >> 1) //get block index in section + }); + if(($y & 1) === 0){ + return $data & 0x0F; + }else{ + return $data >> 4; + } + } + + public function getBlockSkyLight($x, $y, $z){ + $level = ord($this->skyLight{ + (($y >> 4) << 11) //get section index + + ($y << 7) + ($z << 3) + ($x >> 1) //get block index in section + }); + if(($y & 1) === 0){ + return $level & 0x0F; + }else{ + return $level >> 4; + } + } + + public function getBlockLight($x, $y, $z){ + $level = ord($this->light{ + (($y >> 4) << 11) //get section index + + ($y << 7) + ($z << 3) + ($x >> 1) //get block index in section + }); + if(($y & 1) === 0){ + return $level & 0x0F; + }else{ + return $level >> 4; + } + } + + public function getBiome(){ + return 0; //TODO + } + + public function getHighestBlockAt($x, $z){ + return 127; //TODO + } +} \ No newline at end of file diff --git a/src/pocketmine/level/format/mcregion/McRegion.php b/src/pocketmine/level/format/mcregion/McRegion.php new file mode 100644 index 000000000..8c9a7ec28 --- /dev/null +++ b/src/pocketmine/level/format/mcregion/McRegion.php @@ -0,0 +1,285 @@ + 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::BIG_ENDIAN); + $nbt->setData(new Compound(null, [ + "Data" => $levelData + ])); + $buffer = $nbt->writeCompressed(); + @file_put_contents($path . "level.dat", $buffer); + } + + public static function getRegionIndex($chunkX, $chunkZ, &$x, &$z){ + $x = $chunkX >> 5; + $z = $chunkZ >> 5; + } + + public function unloadChunks(){ + $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){ + $index = Level::chunkHash($chunkX, $chunkZ); + if(isset($this->chunks[$index])){ + return true; + } + $regionX = $regionZ = null; + self::getRegionIndex($chunkX, $chunkZ, $regionX, $regionZ); + $this->loadRegion($regionX, $regionZ); + + $this->level->timings->syncChunkLoadDataTimer->startTiming(); + $chunk = $this->getRegion($regionX, $regionZ)->readChunk($chunkX - $regionX * 32, $chunkZ - $regionZ * 32, $create); //generate empty chunk if not loaded + $this->level->timings->syncChunkLoadDataTimer->stopTiming(); + + if($chunk instanceof Chunk){ + $this->chunks[$index] = $chunk; + }else{ + return false; + } + } + + public function unloadChunk($x, $z, $safe = true){ + $chunk = $this->getChunk($x, $z, false); + if($chunk instanceof Chunk){ + if($safe === true and $this->isChunkLoaded($x, $z)){ + foreach($chunk->getEntities() as $entity){ + if($entity instanceof Player){ + return false; + } + } + } + + foreach($chunk->getEntities() as $entity){ + $entity->close(); + } + + foreach($chunk->getTiles() as $tile){ + $tile->close(); + } + + $this->chunks[$index = Level::chunkHash($x, $z)] = null; + + unset($this->chunks[$index]); + } + return true; + } + + public function saveChunk($x, $z){ + if($this->isChunkLoaded($x, $z)){ + $this->getRegion($x >> 5, $z >> 5)->writeChunk($this->getChunk($x, $z)); + + return true; + } + + return false; + } + + /** + * @param $x + * @param $z + * + * @return RegionLoader + */ + protected function getRegion($x, $z){ + $index = $x . ":" . $z; + + return isset($this->regions[$index]) ? $this->regions[$index] : null; + } + + /** + * @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; + } + } + + public function setChunk($chunkX, $chunkZ, SimpleChunk $chunk){ + return; + //TODO! + + + if($chunk->isGenerated() === false){ + $this->unloadChunk($chunkX, $chunkZ, false); + $regionX = $regionZ = null; + self::getRegionIndex($chunkX, $chunkZ, $regionX, $regionZ); + $this->loadRegion($regionX, $regionZ); + $region = $this->getRegion($regionX, $regionZ); + $region->removeChunk($chunkX - $region->getX() * 32, $chunkZ - $region->getZ() * 32); + $this->loadChunk($chunkX, $chunkZ); + }else{ + $newChunk = $this->getChunk($chunkX, $chunkZ, true); + for($y = 0; $y < 8; ++$y){ + /*$section = new ChunkSection(new Compound(null, [ + "Y" => new Byte("Y", $y), + "Blocks" => new ByteArray("Blocks", $chunk->getSectionIds($y)), + "Data" => new ByteArray("Data", $chunk->getSectionData($y)), + "SkyLight" => new ByteArray("SkyLight", str_repeat("\xff", 2048)), //TODO + "BlockLight" => new ByteArray("BlockLight", str_repeat("\x00", 2048)) //TODO + ])); + $newChunk->setSection($y, $section);*/ + } + if($chunk->isPopulated()){ + $newChunk->setPopulated(1); + } + $this->chunks[Level::chunkHash($chunkX, $chunkZ)] = $newChunk; + $this->saveChunk($chunkX, $chunkZ); + } + } + + public function createChunkSection($Y){ + return new ChunkSection( + $Y, + str_repeat("\xff", 4096), + $half = str_repeat("\xff", 2048), + $half, + $half + ); + } + + public function isChunkGenerated($chunkX, $chunkZ){ + if(($region = $this->getRegion($chunkX >> 5, $chunkZ >> 5)) instanceof RegionLoader){ + return $region->chunkExists($chunkX - $region->getX() * 32, $chunkZ - $region->getZ() * 32); + } + + return false; + } + + public function isChunkPopulated($chunkX, $chunkZ){ + $chunk = $this->getChunk($chunkX, $chunkZ); + if($chunk instanceof Chunk){ + return $chunk->isPopulated(); + }else{ + return false; + } + } + + protected function loadRegion($x, $z){ + $index = $x . ":" . $z; + if(isset($this->regions[$index])){ + return true; + } + + $this->regions[$index] = new RegionLoader($this, $x, $z); + + return true; + } + + public function close(){ + $this->unloadChunks(); + foreach($this->regions as $index => $region){ + $region->close(); + unset($this->regions[$index]); + } + } +} \ No newline at end of file diff --git a/src/pocketmine/level/format/mcregion/RegionLoader.php b/src/pocketmine/level/format/mcregion/RegionLoader.php new file mode 100644 index 000000000..860eeea43 --- /dev/null +++ b/src/pocketmine/level/format/mcregion/RegionLoader.php @@ -0,0 +1,375 @@ +x = $regionX; + $this->z = $regionZ; + $this->levelProvider = $level; + $this->filePath = $this->levelProvider->getPath() . "region/r.$regionX.$regionZ.mcr"; + touch($this->filePath); + $this->filePointer = fopen($this->filePath, "r+b"); + flock($this->filePointer, LOCK_EX); + stream_set_read_buffer($this->filePointer, 1024 * 16); //16KB + stream_set_write_buffer($this->filePointer, 1024 * 16); //16KB + if(!file_exists($this->filePath)){ + $this->createBlank(); + }else{ + $this->loadLocationTable(); + } + } + + public function __destruct(){ + if(is_resource($this->filePointer)){ + $this->cleanGarbage(); + $this->writeLocationTable(); + flock($this->filePointer, LOCK_UN); + fclose($this->filePointer); + } + } + + protected function isChunkGenerated($index){ + return !($this->locationTable[$index][0] === 0 or $this->locationTable[$index][1] === 0); + } + + public function readChunk($x, $z, $generate = true){ + $index = self::getChunkOffset($x, $z); + if($index < 0 or $index >= 4096){ + //Regenerate chunk due to corruption + $this->locationTable[$index][0] = 0; + $this->locationTable[$index][1] = 1; + } + + if(!$this->isChunkGenerated($index)){ + if($generate === true){ + //Allocate space + $this->locationTable[$index][0] = ++$this->lastSector; + $this->locationTable[$index][1] = 1; + fseek($this->filePointer, $this->locationTable[$index][0] << 12); + fwrite($this->filePointer, str_pad(Binary::writeInt(-1) . chr(self::COMPRESSION_ZLIB), 4096, "\x00", STR_PAD_RIGHT)); + $this->writeLocationIndex($index); + }else{ + return false; + } + } + + fseek($this->filePointer, $this->locationTable[$index][0] << 12); + $length = Binary::readInt(fread($this->filePointer, 4)); + $compression = ord(fgetc($this->filePointer)); + + if($length <= 0){ //Not yet generated + $this->generateChunk($x, $z); + fseek($this->filePointer, $this->locationTable[$index][0] << 12); + $length = Binary::readInt(fread($this->filePointer, 4)); + $compression = ord(fgetc($this->filePointer)); + } + + if($length > ($this->locationTable[$index][1] << 12)){ //Invalid chunk, bigger than defined number of sectors + trigger_error("Corrupted bigger chunk detected", E_USER_WARNING); + $this->locationTable[$index][1] = $length >> 12; + $this->writeLocationIndex($index); + }elseif($compression !== self::COMPRESSION_ZLIB and $compression !== self::COMPRESSION_GZIP){ + trigger_error("Invalid compression type", E_USER_WARNING); + return false; + } + + $nbt = new NBT(NBT::BIG_ENDIAN); + $nbt->readCompressed(fread($this->filePointer, $length - 1), $compression); + $chunk = $nbt->getData(); + + + if(!isset($chunk->Level) or !($chunk->Level instanceof Compound)){ + return false; + } + + return new Chunk($this->levelProvider, $chunk->Level); + } + + public function chunkExists($x, $z){ + return $this->isChunkGenerated(self::getChunkOffset($x, $z)); + } + + public function generateChunk($x, $z){ + $nbt = new Compound("Level", []); + $nbt->xPos = new Int("xPos", ($this->getX() * 32) + $x); + $nbt->zPos = new Int("zPos", ($this->getZ() * 32) + $z); + $nbt->LastUpdate = new Long("LastUpdate", 0); + $nbt->LightPopulated = new Byte("LightPopulated", 0); + $nbt->TerrainPopulated = new Byte("TerrainPopulated", 0); + $nbt->V = new Byte("V", self::VERSION); + $nbt->InhabitedTime = new Long("InhabitedTime", 0); + $nbt->Biomes = new ByteArray("Biomes", str_repeat(Binary::writeByte(-1), 256)); + $nbt->HeightMap = new IntArray("HeightMap", array_fill(0, 256, 127)); + $nbt->BiomeColors = new IntArray("BiomeColors", array_fill(0, 256, Binary::readInt("\x00\x85\xb2\x4a"))); + + $nbt->Blocks = new ByteArray("Blocks", str_repeat("\x00", 32768)); + $nbt->Blocks = new ByteArray("Data", $half = str_repeat("\x00", 16384)); + $nbt->SkyLight = new ByteArray("SkyLight", $half); + $nbt->BlockLight = new ByteArray("BlockLight", $half); + + $nbt->Entities = new Enum("Entities", []); + $nbt->Entities->setTagType(NBT::TAG_Compound); + $nbt->TileEntities = new Enum("TileEntities", []); + $nbt->TileEntities->setTagType(NBT::TAG_Compound); + $nbt->TileTicks = new Enum("TileTicks", []); + $nbt->TileTicks->setTagType(NBT::TAG_Compound); + $this->saveChunk($x, $z, $nbt); + } + + protected function saveChunk($x, $z, Compound $nbt){ + $writer = new NBT(NBT::BIG_ENDIAN); + $nbt->setName("Level"); + $writer->setData(new Compound("", array("Level" => $nbt))); + $chunkData = $writer->writeCompressed(ZLIB_ENCODING_DEFLATE, self::$COMPRESSION_LEVEL); + $length = strlen($chunkData) + 1; + $sectors = (int) ceil(($length + 4) / 4096); + $index = self::getChunkOffset($x, $z); + if($this->locationTable[$index][1] < $sectors){ + $this->locationTable[$index][0] = $this->lastSector += $sectors; //The GC will clean this shift later + } + $this->locationTable[$index][1] = $sectors; + + fseek($this->filePointer, $this->locationTable[$index][0] << 12); + fwrite($this->filePointer, str_pad(Binary::writeInt($length) . chr(self::COMPRESSION_ZLIB) . $chunkData, $sectors << 12, "\x00", STR_PAD_RIGHT)); + $this->writeLocationIndex($index); + } + + public function removeChunk($x, $z){ + $index = self::getChunkOffset($x, $z); + $this->locationTable[$index][0] = 0; + $this->locationTable[$index][1] = 0; + } + + public function writeChunk(Chunk $chunk){ + //TODO + $nbt = $chunk->getNBT(); + $nbt->Sections = new Enum("Sections", []); + $nbt->Sections->setTagType(NBT::TAG_Compound); + foreach($chunk->getSections() as $section){ + $nbt->Sections[$section->getY()] = new Compound(null, [ + "Y" => new Byte("Y", $section->getY()), + "Blocks" => new ByteArray("Blocks", $section->getIdArray()), + "Data" => new ByteArray("Data", $section->getDataArray()), + "BlockLight" => new ByteArray("BlockLight", $section->getLightArray()), + "SkyLight" => new ByteArray("SkyLight", $section->getSkyLightArray()) + ]); + } + + $nbt->Biomes = new ByteArray("Biomes", $chunk->getBiomeIdArray()); + $nbt->BiomeColors = new IntArray("BiomeColors", $chunk->getBiomeColorArray()); + + $entities = []; + + foreach($chunk->getEntities() as $entity){ + if(!($entity instanceof Player) and $entity->closed !== true){ + $entity->saveNBT(); + $entities[] = $entity->namedtag; + } + } + + $nbt->Entities = new Enum("Entities", $entities); + $nbt->Entities->setTagType(NBT::TAG_Compound); + + + $tiles = []; + foreach($chunk->getTiles() as $tile){ + if($tile->closed !== true){ + $tile->saveNBT(); + $tiles[] = $tile->namedtag; + } + } + + $nbt->Entities = new Enum("TileEntities", $tiles); + $nbt->Entities->setTagType(NBT::TAG_Compound); + + $this->saveChunk($chunk->getX() - ($this->getX() * 32), $chunk->getZ() - ($this->getZ() * 32), $nbt); + } + + protected static function getChunkOffset($x, $z){ + return $x + ($z << 5); + } + + public function close(){ + $this->writeLocationTable(); + flock($this->filePointer, LOCK_UN); + fclose($this->filePointer); + } + + public function doSlowCleanUp(){ + for($i = 0; $i < 1024; ++$i){ + if($this->locationTable[$i][0] === 0 or $this->locationTable[$i][1] === 0){ + continue; + } + fseek($this->filePointer, $this->locationTable[$i][0] << 12); + $chunk = fread($this->filePointer, $this->locationTable[$i][1] << 12); + $length = Binary::readInt(substr($chunk, 0, 4)); + if($length <= 1){ + $this->locationTable[$i] = array(0, 0, 0); //Non-generated chunk, remove it from index + } + $chunk = zlib_decode(substr($chunk, 5)); + if(strlen($chunk) <= 1){ + $this->locationTable[$i] = array(0, 0, 0); //Corrupted chunk, remove it + continue; + } + $chunk = chr(self::COMPRESSION_ZLIB) . zlib_encode($chunk, 15, 9); + $chunk = Binary::writeInt(strlen($chunk)) . $chunk; + $sectors = (int) ceil(strlen($chunk) / 4096); + if($sectors > $this->locationTable[$i][1]){ + $this->locationTable[$i][0] = $this->lastSector += $sectors; + } + fseek($this->filePointer, $this->locationTable[$i][0] << 12); + fwrite($this->filePointer, str_pad($chunk, $sectors << 12, "\x00", STR_PAD_RIGHT)); + } + $this->writeLocationTable(); + $n = $this->cleanGarbage(); + $this->writeLocationTable(); + + return $n; + } + + private function cleanGarbage(){ + $sectors = []; + foreach($this->locationTable as $index => $data){ //Calculate file usage + if($data[0] === 0 or $data[1] === 0){ + $this->locationTable[$index] = array(0, 0, 0); + continue; + } + for($i = 0; $i < $data[1]; ++$i){ + $sectors[$data[0]] = $index; + } + } + + if(count($sectors) === ($this->lastSector - 2)){ //No collection needed + return 0; + } + + ksort($sectors); + $shift = 0; + $lastSector = 1; //First chunk - 1 + + fseek($this->filePointer, 8192); + $sector = 2; + foreach($sectors as $sector => $index){ + if(($sector - $lastSector) > 1){ + $shift += $sector - $lastSector - 1; + } + if($shift > 0){ + fseek($this->filePointer, $sector << 12); + $old = fread($this->filePointer, 4096); + fseek($this->filePointer, ($sector - $shift) << 12); + fwrite($this->filePointer, $old, 4096); + } + $this->locationTable[$index][0] -= $shift; + $lastSector = $sector; + } + ftruncate($this->filePointer, ($sector + 1) << 12); //Truncate to the end of file written + return $shift; + } + + private function loadLocationTable(){ + fseek($this->filePointer, 0); + $this->lastSector = 1; + $table = fread($this->filePointer, 4 * 1024 * 2); + for($i = 0; $i < 1024; ++$i){ + $index = Binary::readInt(substr($table, $i << 2, 4)); + $this->locationTable[$i] = array(($index & ~0xff) >> 8, $index & 0xff, Binary::readInt(substr($table, 4096 + ($i << 2), 4))); + if(($this->locationTable[$i][0] + $this->locationTable[$i][1] - 1) > $this->lastSector){ + $this->lastSector = $this->locationTable[$i][0] + $this->locationTable[$i][1] - 1; + } + } + } + + private function writeLocationTable(){ + $table = ""; + + for($i = 0; $i < 1024; ++$i){ + $table .= Binary::writeInt(($this->locationTable[$i][0] << 8) | $this->locationTable[$i][1]); + } + for($i = 0; $i < 1024; ++$i){ + $table .= Binary::writeInt($this->locationTable[$i][2]); + } + fseek($this->filePointer, 0); + fwrite($this->filePointer, $table, 4096 * 2); + } + + private function writeLocationIndex($index){ + fseek($this->filePointer, $index << 2); + fwrite($this->filePointer, Binary::writeInt(($this->locationTable[$index][0] << 8) | $this->locationTable[$index][1]), 4); + fseek($this->filePointer, 4096 + ($index << 2)); + fwrite($this->filePointer, Binary::writeInt($this->locationTable[$index][2]), 4); + } + + private function createBlank(){ + fseek($this->filePointer, 0); + ftruncate($this->filePointer, 0); + $this->lastSector = 1; + $table = ""; + for($i = 0; $i < 1024; ++$i){ + $this->locationTable[$i] = array(0, 0); + $table .= Binary::writeInt(0); + } + + $time = time(); + for($i = 0; $i < 1024; ++$i){ + $this->locationTable[$i][2] = $time; + $table .= Binary::writeInt($time); + } + + fwrite($this->filePointer, $table, 4096 * 2); + } + + public function getX(){ + return $this->x; + } + + public function getZ(){ + return $this->z; + } + +} \ No newline at end of file From afe44e6c6fabc92ac4e4fdb1e211b50b909378a3 Mon Sep 17 00:00:00 2001 From: Shoghi Cervantes Date: Tue, 29 Jul 2014 18:54:40 +0200 Subject: [PATCH 02/13] Added setting to select the default level format --- src/pocketmine/Server.php | 5 ++++- src/pocketmine/level/format/LevelProvider.php | 7 +++++++ src/pocketmine/level/format/LevelProviderManager.php | 7 ++++++- src/pocketmine/level/format/anvil/Anvil.php | 3 +++ src/pocketmine/level/format/mcregion/McRegion.php | 3 +++ src/pocketmine/resources/pocketmine.yml | 4 ++++ 6 files changed, 27 insertions(+), 2 deletions(-) diff --git a/src/pocketmine/Server.php b/src/pocketmine/Server.php index fa898a47c..07affa1dd 100644 --- a/src/pocketmine/Server.php +++ b/src/pocketmine/Server.php @@ -1052,7 +1052,9 @@ class Server{ } } - $provider = "pocketmine\\level\\format\\anvil\\Anvil"; + if(($provider = LevelProviderManager::getProviderByName($this->getProperty("level-settings.default-format", "mcregion"))) === null){ + $provider = "pocketmine\\level\\format\\mcregion\\McRegion"; + } $path = $this->getDataPath() . "worlds/" . $name . "/"; /** @var \pocketmine\level\format\LevelProvider $provider */ @@ -1500,6 +1502,7 @@ class Server{ $this->generationManager = new GenerationRequestManager($this); LevelProviderManager::addProvider($this, "pocketmine\\level\\format\\anvil\\Anvil"); + LevelProviderManager::addProvider($this, "pocketmine\\level\\format\\mcregion\\McRegion"); Generator::addGenerator("pocketmine\\level\\generator\\Flat", "flat"); diff --git a/src/pocketmine/level/format/LevelProvider.php b/src/pocketmine/level/format/LevelProvider.php index ea7b70f62..438a4fb5f 100644 --- a/src/pocketmine/level/format/LevelProvider.php +++ b/src/pocketmine/level/format/LevelProvider.php @@ -32,6 +32,13 @@ interface LevelProvider{ */ public function __construct(Level $level, $path); + /** + * Returns the full provider name, like "anvil" or "mcregion", will be used to find the correct format. + * + * @return string + */ + public static function getProviderName(); + /** @return string */ public function getPath(); diff --git a/src/pocketmine/level/format/LevelProviderManager.php b/src/pocketmine/level/format/LevelProviderManager.php index d87335f85..503300f1c 100644 --- a/src/pocketmine/level/format/LevelProviderManager.php +++ b/src/pocketmine/level/format/LevelProviderManager.php @@ -36,7 +36,7 @@ abstract class LevelProviderManager{ if(!is_subclass_of($class, "pocketmine\\level\\format\\LevelProvider")){ throw new \Exception("Class is not a subclass of LevelProvider"); } - self::$providers[strtolower($class)] = $class; + self::$providers[strtolower($class::getProviderName())] = $class; } /** @@ -56,4 +56,9 @@ abstract class LevelProviderManager{ return null; } + + public static function getProviderByName($name){ + $name = trim(strtolower($name)); + return isset(self::$providers[$name]) ? self::$providers[$name] : null; + } } \ No newline at end of file diff --git a/src/pocketmine/level/format/anvil/Anvil.php b/src/pocketmine/level/format/anvil/Anvil.php index cbe73e226..e9c4b4bcf 100644 --- a/src/pocketmine/level/format/anvil/Anvil.php +++ b/src/pocketmine/level/format/anvil/Anvil.php @@ -42,6 +42,9 @@ class Anvil extends BaseLevelProvider{ /** @var Chunk[] */ protected $chunks = []; + public static function getProviderName(){ + return "anvil"; + } public static function isValid($path){ $isValid = (file_exists($path . "/level.dat") and is_dir($path . "/region/")); diff --git a/src/pocketmine/level/format/mcregion/McRegion.php b/src/pocketmine/level/format/mcregion/McRegion.php index 8c9a7ec28..098cdf5b1 100644 --- a/src/pocketmine/level/format/mcregion/McRegion.php +++ b/src/pocketmine/level/format/mcregion/McRegion.php @@ -41,6 +41,9 @@ class McRegion extends BaseLevelProvider{ /** @var Chunk[] */ protected $chunks = []; + public static function getProviderName(){ + return "mcregion"; + } public static function isValid($path){ $isValid = (file_exists($path . "/level.dat") and is_dir($path . "/region/")); diff --git a/src/pocketmine/resources/pocketmine.yml b/src/pocketmine/resources/pocketmine.yml index a0ebd31a9..2a8866cdc 100644 --- a/src/pocketmine/resources/pocketmine.yml +++ b/src/pocketmine/resources/pocketmine.yml @@ -18,6 +18,10 @@ debug: #Enables /status commands: false +level-settings: + default-format: mcregion #The default format that levels will use when created + convert-format: false #If true, converts from a format that is not the default to the default format on load + chunk-sending: per-tick: 4 compression-level: 9 From ce018fd7c8c62a4d1aaf469674f880469d04574d Mon Sep 17 00:00:00 2001 From: Shoghi Cervantes Date: Tue, 29 Jul 2014 18:55:27 +0200 Subject: [PATCH 03/13] Bumped API version to 1.2.0 (backwards-compatible changes, related to level formats) --- src/pocketmine/PocketMine.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pocketmine/PocketMine.php b/src/pocketmine/PocketMine.php index ede38e5c6..e277db86c 100644 --- a/src/pocketmine/PocketMine.php +++ b/src/pocketmine/PocketMine.php @@ -74,7 +74,7 @@ namespace pocketmine { use raklib\RakLib; const VERSION = "Alpha_1.4dev"; - const API_VERSION = "1.1.0"; + const API_VERSION = "1.2.0"; const CODENAME = "絶好(Zekkou)ケーキ(Cake)"; const MINECRAFT_VERSION = "v0.9.5 alpha"; const PHP_VERSION = "5.5"; From b0aad89e7af854d128000dd9d27aba27d9317e9b Mon Sep 17 00:00:00 2001 From: Shoghi Cervantes Date: Tue, 29 Jul 2014 19:11:09 +0200 Subject: [PATCH 04/13] Fixed a few typos in McRegion --- src/pocketmine/level/format/mcregion/Chunk.php | 15 ++++----------- .../level/format/mcregion/RegionLoader.php | 2 +- 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/src/pocketmine/level/format/mcregion/Chunk.php b/src/pocketmine/level/format/mcregion/Chunk.php index b6f4cbd06..c9cc1d553 100644 --- a/src/pocketmine/level/format/mcregion/Chunk.php +++ b/src/pocketmine/level/format/mcregion/Chunk.php @@ -61,13 +61,6 @@ class Chunk extends BaseChunk{ $this->nbt->TileTicks->setTagType(NBT::TAG_Compound); } - if(isset($this->nbt->Sections) and $this->nbt->Sections instanceof Enum){ - $this->nbt->Sections->setTagType(NBT::TAG_Compound); - }else{ - $this->nbt->Sections = new Enum("Sections", []); - $this->nbt->Sections->setTagType(NBT::TAG_Compound); - } - if(!isset($this->nbt->Biomes) or !($this->nbt->Biomes instanceof ByteArray)){ $this->nbt->Biomes = new ByteArray("Biomes", str_repeat("\x01", 256)); } @@ -83,10 +76,10 @@ class Chunk extends BaseChunk{ $offset = 0; for($i = 0; $i < 256; ++$i){ - list($blocks[0][$i], $blocks[1][$i], $blocks[2][$i], $blocks[3][$i], $blocks[4][$i], $blocks[5][$i], $blocks[6][$i], $blocks[7][$i]) = str_split(substr($this->nbt->Blocks, $offset << 1, 128), 16); - list($datas[0][$i], $datas[1][$i], $datas[2][$i], $datas[3][$i], $datas[4][$i], $datas[5][$i], $datas[6][$i], $datas[7][$i]) = str_split(substr($this->nbt->Data, $offset, 64), 8); - list($skyLight[0][$i], $skyLight[1][$i], $skyLight[2][$i], $skyLight[3][$i], $skyLight[4][$i], $skyLight[5][$i], $skyLight[6][$i], $skyLight[7][$i]) = str_split(substr($this->nbt->SkyLight, $offset, 64), 8); - list($blockLight[0][$i], $blockLight[1][$i], $blockLight[2][$i], $blockLight[3][$i], $blockLight[4][$i], $blockLight[5][$i], $blockLight[6][$i], $blockLight[7][$i]) = str_split(substr($this->nbt->BlockLight, $offset, 64), 8); + list($blocks[0][$i], $blocks[1][$i], $blocks[2][$i], $blocks[3][$i], $blocks[4][$i], $blocks[5][$i], $blocks[6][$i], $blocks[7][$i]) = str_split(substr($this->nbt["Blocks"], $offset << 1, 128), 16); + list($datas[0][$i], $datas[1][$i], $datas[2][$i], $datas[3][$i], $datas[4][$i], $datas[5][$i], $datas[6][$i], $datas[7][$i]) = str_split(substr($this->nbt["Data"], $offset, 64), 8); + list($skyLight[0][$i], $skyLight[1][$i], $skyLight[2][$i], $skyLight[3][$i], $skyLight[4][$i], $skyLight[5][$i], $skyLight[6][$i], $skyLight[7][$i]) = str_split(substr($this->nbt["SkyLight"], $offset, 64), 8); + list($blockLight[0][$i], $blockLight[1][$i], $blockLight[2][$i], $blockLight[3][$i], $blockLight[4][$i], $blockLight[5][$i], $blockLight[6][$i], $blockLight[7][$i]) = str_split(substr($this->nbt["BlockLight"], $offset, 64), 8); $offset += 64; } diff --git a/src/pocketmine/level/format/mcregion/RegionLoader.php b/src/pocketmine/level/format/mcregion/RegionLoader.php index 860eeea43..40d27b5f4 100644 --- a/src/pocketmine/level/format/mcregion/RegionLoader.php +++ b/src/pocketmine/level/format/mcregion/RegionLoader.php @@ -149,7 +149,7 @@ class RegionLoader{ $nbt->BiomeColors = new IntArray("BiomeColors", array_fill(0, 256, Binary::readInt("\x00\x85\xb2\x4a"))); $nbt->Blocks = new ByteArray("Blocks", str_repeat("\x00", 32768)); - $nbt->Blocks = new ByteArray("Data", $half = str_repeat("\x00", 16384)); + $nbt->Data = new ByteArray("Data", $half = str_repeat("\x00", 16384)); $nbt->SkyLight = new ByteArray("SkyLight", $half); $nbt->BlockLight = new ByteArray("BlockLight", $half); From 8fafb85784e0d09c9a11957d525a37d4777d31ad Mon Sep 17 00:00:00 2001 From: Shoghi Cervantes Date: Tue, 29 Jul 2014 19:36:41 +0200 Subject: [PATCH 05/13] Added proper chunk saving in McRegion --- .../level/format/mcregion/RegionLoader.php | 29 ++++++++++++++----- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/src/pocketmine/level/format/mcregion/RegionLoader.php b/src/pocketmine/level/format/mcregion/RegionLoader.php index 40d27b5f4..bfaceed8d 100644 --- a/src/pocketmine/level/format/mcregion/RegionLoader.php +++ b/src/pocketmine/level/format/mcregion/RegionLoader.php @@ -191,16 +191,29 @@ class RegionLoader{ $nbt = $chunk->getNBT(); $nbt->Sections = new Enum("Sections", []); $nbt->Sections->setTagType(NBT::TAG_Compound); - foreach($chunk->getSections() as $section){ - $nbt->Sections[$section->getY()] = new Compound(null, [ - "Y" => new Byte("Y", $section->getY()), - "Blocks" => new ByteArray("Blocks", $section->getIdArray()), - "Data" => new ByteArray("Data", $section->getDataArray()), - "BlockLight" => new ByteArray("BlockLight", $section->getLightArray()), - "SkyLight" => new ByteArray("SkyLight", $section->getSkyLightArray()) - ]); + + $blocks = ""; + $data = ""; + $skyLight = ""; + $blockLight = ""; + $sections = $chunk->getSections(); + + for($i = 0; $i < 256; ++$i){ + $x = $i & 0x0F; + $z = $i >> 4; + for($s = 0; $s < 0; ++$s){ + $blocks .= $sections[$s]->getBlockIdColumn($x, $z); + $data .= $sections[$s]->getBlockDataColumn($x, $z); + $skyLight .= $sections[$s]->getBlockSkyLightColumn($x, $z); + $blockLight .= $sections[$s]->getBlockLightColumn($x, $z); + } } + $nbt->Blocks = new ByteArray("Blocks", $blocks); + $nbt->Data = new ByteArray("Data", $data); + $nbt->SkyLight = new ByteArray("SkyLight", $skyLight); + $nbt->BlockLight = new ByteArray("BlockLight", $blockLight); + $nbt->Biomes = new ByteArray("Biomes", $chunk->getBiomeIdArray()); $nbt->BiomeColors = new IntArray("BiomeColors", $chunk->getBiomeColorArray()); From 016b08ecf2832a988e8cfd44c2596f976e22ec6c Mon Sep 17 00:00:00 2001 From: Shoghi Cervantes Date: Wed, 30 Jul 2014 12:57:00 +0200 Subject: [PATCH 06/13] Added block order detection, added support for non-sectioned level formats --- src/pocketmine/Player.php | 3 +- src/pocketmine/level/Level.php | 71 ++++- src/pocketmine/level/format/Chunk.php | 220 +-------------- src/pocketmine/level/format/FullChunk.php | 263 ++++++++++++++++++ src/pocketmine/level/format/LevelProvider.php | 13 + src/pocketmine/level/format/anvil/Anvil.php | 8 + .../level/format/generic/BaseChunk.php | 150 ++++------ .../level/format/generic/BaseFullChunk.php | 260 +++++++++++++++++ .../level/format/mcregion/Chunk.php | 167 +++++++---- .../level/format/mcregion/ChunkSection.php | 184 ------------ .../level/format/mcregion/McRegion.php | 8 + .../level/format/mcregion/RegionLoader.php | 28 +- 12 files changed, 796 insertions(+), 579 deletions(-) create mode 100644 src/pocketmine/level/format/FullChunk.php create mode 100644 src/pocketmine/level/format/generic/BaseFullChunk.php delete mode 100644 src/pocketmine/level/format/mcregion/ChunkSection.php diff --git a/src/pocketmine/Player.php b/src/pocketmine/Player.php index 9d2a06393..d18da0889 100644 --- a/src/pocketmine/Player.php +++ b/src/pocketmine/Player.php @@ -57,6 +57,7 @@ use pocketmine\inventory\InventoryHolder; use pocketmine\inventory\SimpleTransactionGroup; use pocketmine\inventory\StonecutterShapelessRecipe; use pocketmine\item\Item; +use pocketmine\level\format\LevelProvider; use pocketmine\level\Level; use pocketmine\level\Position; use pocketmine\math\Vector3; @@ -601,7 +602,7 @@ class Player extends Human implements CommandSender, InventoryHolder, IPlayer{ $this->usedChunks[$index] = [false, 0]; $this->getLevel()->useChunk($X, $Z, $this); - $this->getLevel()->requestChunk($X, $Z, $this); + $this->getLevel()->requestChunk($X, $Z, $this, LevelProvider::ORDER_ZXY); } } diff --git a/src/pocketmine/level/Level.php b/src/pocketmine/level/Level.php index 1bbcf47f6..f80021cbd 100644 --- a/src/pocketmine/level/Level.php +++ b/src/pocketmine/level/Level.php @@ -136,6 +136,9 @@ class Level implements ChunkManager, Metadatable{ /** @var BlockMetadataStore */ private $blockMetadata; + private $useSections; + private $blockOrder; + protected $chunkTickRadius; protected $chunkTickList = []; protected $chunksPerTick; @@ -199,6 +202,9 @@ class Level implements ChunkManager, Metadatable{ $this->levelId = static::$levelIdCounter++; $this->blockMetadata = new BlockMetadataStore($this); $this->server = $server; + + /** @var LevelProvider $provider */ + if(is_subclass_of($provider, "pocketmine\\level\\format\\LevelProvider", true)){ $this->provider = new $provider($this, $path); }else{ @@ -208,6 +214,9 @@ class Level implements ChunkManager, Metadatable{ $generator = Generator::getGenerator($this->provider->getGenerator()); $this->server->getGenerationManager()->openLevel($this, $generator, $this->provider->getGeneratorOptions()); + $this->blockOrder = $provider::getProviderOrder(); + $this->useSections = $provider::usesChunkSection(); + $this->folderName = $name; $this->updateQueue = new ReversePriorityQueue(); $this->updateQueue->setExtractFlags(\SplPriorityQueue::EXTR_BOTH); @@ -220,6 +229,7 @@ class Level implements ChunkManager, Metadatable{ $this->chunkTickList = []; $this->clearChunksOnTick = (bool) $this->server->getProperty("chunk-ticking.clear-tick-list", false); $this->timings = new LevelTimings($this); + } /** @@ -509,9 +519,27 @@ class Level implements ChunkManager, Metadatable{ $chunk = $this->getChunkAt($chunkX, $chunkZ, true); - foreach($chunk->getSections() as $section){ - if(!($section instanceof EmptyChunkSection)){ - $Y = $section->getY(); + if($this->useSections){ + foreach($chunk->getSections() as $section){ + if(!($section instanceof EmptyChunkSection)){ + $Y = $section->getY(); + $k = mt_rand(0, PHP_INT_MAX); + for($i = 0; $i < 3; ++$i){ + $j = $k >> 2; + $x = $j & 0x0f; + $y = ($j >> 8) & 0x0f; + $z = ($j >> 16) & 0x0f; + $k %= 1073741827; + $blockId = $section->getBlockId($x, $y, $z); + if(isset($this->randomTickBlocks[$blockId])){ + $block = Block::get($blockId, $section->getBlockData($x, $y, $z), new Position($chunkX * 16 + $x, ($Y << 4) + $y, $chunkZ * 16 + $z, $this)); + $block->onUpdate(self::BLOCK_UPDATE_RANDOM); + } + } + } + } + }else{ + for($Y = 0; $Y < 8; ++$Y){ $k = mt_rand(0, PHP_INT_MAX); for($i = 0; $i < 3; ++$i){ $j = $k >> 2; @@ -519,9 +547,9 @@ class Level implements ChunkManager, Metadatable{ $y = ($j >> 8) & 0x0f; $z = ($j >> 16) & 0x0f; $k %= 1073741827; - $blockId = $section->getBlockId($x, $y, $z); + $blockId = $chunk->getBlockId($x, $y + ($Y << 4), $z); if(isset($this->randomTickBlocks[$blockId])){ - $block = Block::get($blockId, $section->getBlockData($x, $y, $z), new Position($chunkX * 16 + $x, $Y * 16 + $y, $chunkZ * 16 + $z, $this)); + $block = Block::get($blockId, $chunk->getBlockData($x, $y + ($Y << 4), $z), new Position($chunkX * 16 + $x, ($Y << 4) + $y, $chunkZ * 16 + $z, $this)); $block->onUpdate(self::BLOCK_UPDATE_RANDOM); } } @@ -1372,13 +1400,36 @@ class Level implements ChunkManager, Metadatable{ $this->server->getPluginManager()->callEvent(new SpawnChangeEvent($this, $previousSpawn)); } - public function requestChunk($x, $z, Player $player){ - $index = Level::chunkHash($x, $z); - if(!isset($this->chunkSendQueue[$index])){ - $this->chunkSendQueue[$index] = []; + public function requestChunk($x, $z, Player $player, $order = LevelProvider::ORDER_ZXY){ + if($this->blockOrder === $order){ + $player->sendChunk($x, $z, $this->getNetworkChunk($x, $z)); + }else{ + $index = Level::chunkHash($x, $z); + if(!isset($this->chunkSendQueue[$index])){ + $this->chunkSendQueue[$index] = []; + } + + $this->chunkSendQueue[$index][spl_object_hash($player)] = $player; + } + } + + protected function getNetworkChunk($x, $z){ + $chunk = $this->getChunkAt($x, $z, true); + $tiles = ""; + $nbt = new NBT(NBT::LITTLE_ENDIAN); + foreach($chunk->getTiles() as $tile){ + if($tile instanceof Spawnable){ + $nbt->setData($tile->getSpawnCompound()); + $tiles .= $nbt->write(); + } } - $this->chunkSendQueue[$index][spl_object_hash($player)] = $player; + $biomeColors = ""; + foreach($chunk->getBiomeColorArray() as $color){ + $biomeColors .= Binary::writeInt($color); + } + + return 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); } protected function processChunkRequest(){ diff --git a/src/pocketmine/level/format/Chunk.php b/src/pocketmine/level/format/Chunk.php index 64c8d893b..3b147190d 100644 --- a/src/pocketmine/level/format/Chunk.php +++ b/src/pocketmine/level/format/Chunk.php @@ -24,217 +24,9 @@ namespace pocketmine\level\format; use pocketmine\entity\Entity; use pocketmine\tile\Tile; -interface Chunk{ +interface Chunk extends FullChunk{ const SECTION_COUNT = 8; - /** - * @return int - */ - public function getX(); - - /** - * @return int - */ - public function getZ(); - - /** - * @return \pocketmine\level\format\LevelProvider - */ - public function getLevel(); - - - /** - * Modifies $blockId and $meta - * - * @param int $x 0-15 - * @param int $y 0-127 - * @param int $z 0-15 - * @param int &$blockId - * @param int &$meta - */ - public function getBlock($x, $y, $z, &$blockId, &$meta = null); - - /** - * @param int $x 0-15 - * @param int $y 0-127 - * @param int $z 0-15 - * @param int $blockId , if null, do not change - * @param int $meta 0-15, if null, do not change - * - */ - public function setBlock($x, $y, $z, $blockId = null, $meta = null); - - /** - * @param int $x 0-15 - * @param int $y 0-127 - * @param int $z 0-15 - * - * @return int 0-255 - */ - public function getBlockId($x, $y, $z); - - /** - * @param int $x 0-15 - * @param int $y 0-127 - * @param int $z 0-15 - * @param int $id 0-255 - */ - public function setBlockId($x, $y, $z, $id); - - /** - * @param int $x 0-15 - * @param int $y 0-127 - * @param int $z 0-15 - * - * @return int 0-15 - */ - public function getBlockData($x, $y, $z); - - /** - * @param int $x 0-15 - * @param int $y 0-127 - * @param int $z 0-15 - * @param int $data 0-15 - */ - public function setBlockData($x, $y, $z, $data); - - /** - * @param int $x 0-15 - * @param int $y 0-127 - * @param int $z 0-15 - * - * @return int 0-15 - */ - public function getBlockSkyLight($x, $y, $z); - - /** - * @param int $x 0-15 - * @param int $y 0-127 - * @param int $z 0-15 - * @param int $level 0-15 - */ - public function setBlockSkyLight($x, $y, $z, $level); - - /** - * @param int $x 0-15 - * @param int $y 0-127 - * @param int $z 0-15 - * - * @return int 0-15 - */ - public function getBlockLight($x, $y, $z); - - /** - * @param int $x 0-15 - * @param int $y 0-127 - * @param int $z 0-15 - * @param int $level 0-15 - */ - public function setBlockLight($x, $y, $z, $level); - - /** - * @param int $x 0-15 - * @param int $z 0-15 - * - * @return int 0-127 - */ - public function getHighestBlockAt($x, $z); - - /** - * @param int $x 0-15 - * @param int $z 0-15 - * - * @return int 0-255 - */ - public function getBiomeId($x, $z); - - /** - * @param int $x 0-15 - * @param int $z 0-15 - * @param int $biomeId 0-255 - */ - public function setBiomeId($x, $z, $biomeId); - - /** - * @param int $x - * @param int $z - * - * @return int[] RGB bytes - */ - public function getBiomeColor($x, $z); - - /** - * @param int $x 0-15 - * @param int $z 0-15 - * @param int $R 0-255 - * @param int $G 0-255 - * @param int $B 0-255 - */ - public function setBiomeColor($x, $z, $R, $G, $B); - - /** - * Thread-safe read-only chunk - * - * @param bool $includeMaxBlockY - * @param bool $includeBiome - * @param bool $includeBiomeTemp - * - * @return ChunkSnapshot - */ - public function getChunkSnapshot($includeMaxBlockY = true, $includeBiome = false, $includeBiomeTemp = false); - - /** - * @param Entity $entity - */ - public function addEntity(Entity $entity); - - /** - * @param Entity $entity - */ - public function removeEntity(Entity $entity); - - /** - * @param Tile $tile - */ - public function addTile(Tile $tile); - - /** - * @param Tile $tile - */ - public function removeTile(Tile $tile); - - /** - * @return \pocketmine\entity\Entity[] - */ - public function getEntities(); - - /** - * @return \pocketmine\tile\Tile[] - */ - public function getTiles(); - - /** - * @return bool - */ - public function isLoaded(); - - /** - * Loads the chunk - * - * @param bool $generate If the chunk does not exist, generate it - * - * @return bool - */ - public function load($generate = true); - - /** - * @param bool $save - * @param bool $safe If false, unload the chunk even if players are nearby - * - * @return bool - */ - public function unload($save = true, $safe = true); - /** * Tests whether a section (mini-chunk) is empty * @@ -264,14 +56,4 @@ interface Chunk{ */ public function getSections(); - /** - * @return string[] - */ - public function getBiomeIdArray(); - - /** - * @return int[] - */ - public function getBiomeColorArray(); - } \ No newline at end of file diff --git a/src/pocketmine/level/format/FullChunk.php b/src/pocketmine/level/format/FullChunk.php new file mode 100644 index 000000000..b5c069498 --- /dev/null +++ b/src/pocketmine/level/format/FullChunk.php @@ -0,0 +1,263 @@ +getLevel()->getLevel()->timings->syncChunkLoadTileEntitiesTimer->stopTiming(); } - public function getX(){ - return $this->x; - } - - public function getZ(){ - return $this->z; - } - - /** - * @return LevelProvider - */ - public function getLevel(){ - return $this->level->valid() ? $this->level->get() : null; - } - public function getBlock($x, $y, $z, &$blockId, &$meta = null){ $this->sections[$y >> 4]->getBlock($x, $y & 0x0f, $z, $blockId, $meta); } @@ -220,36 +206,36 @@ abstract class BaseChunk implements Chunk{ } } - public function getBiomeId($x, $z){ - return ord($this->biomeIds{($z << 4) + $x}); - } - - public function setBiomeId($x, $z, $biomeId){ - $this->biomeIds{($z << 4) + $x} = chr($biomeId); - } - - public function getBiomeColor($x, $z){ - $color = $this->biomeColors[($z << 4) + $x] & 0xFFFFFF; - return [$color >> 16, ($color >> 8) & 0xFF, $color & 0xFF]; - } - - public function setBiomeColor($x, $z, $R, $G, $B){ - $this->biomeColors[($z << 4) + $x] = 0 | (($R & 0xFF) << 16) | (($G & 0xFF) << 8) | ($B & 0xFF); - } - - public function getHighestBlockAt($x, $z){ - for($Y = self::SECTION_COUNT - 1; $Y >= 0; --$Y){ - if(!$this->isSectionEmpty($Y)){ - $column = $this->sections[$Y]->getBlockIdColumn($x, $z); - for($y = 15; $y >= 0; --$y){ - if($column{$y} !== "\x00"){ - return $y + ($Y << 4); - } - } - } + public function getBlockIdColumn($x, $z){ + $column = ""; + for($y = 0; $y < Chunk::SECTION_COUNT; ++$y){ + $column .= $this->sections[$y]->getBlockIdColumn($x, $z); } + return $column; + } - return 0; + public function getBlockDataColumn($x, $z){ + $column = ""; + for($y = 0; $y < Chunk::SECTION_COUNT; ++$y){ + $column .= $this->sections[$y]->getBlockDataColumn($x, $z); + } + return $column; + } + + public function getBlockSkyLightColumn($x, $z){ + $column = ""; + for($y = 0; $y < Chunk::SECTION_COUNT; ++$y){ + $column .= $this->sections[$y]->getBlockSkyLightColumn($x, $z); + } + return $column; + } + + public function getBlockLightColumn($x, $z){ + $column = ""; + for($y = 0; $y < Chunk::SECTION_COUNT; ++$y){ + $column .= $this->sections[$y]->getBlockLightColumn($x, $z); + } + return $column; } public function isSectionEmpty($fY){ @@ -268,54 +254,40 @@ abstract class BaseChunk implements Chunk{ } } - public function addEntity(Entity $entity){ - $this->entities[$entity->getID()] = $entity; - } - - public function removeEntity(Entity $entity){ - unset($this->entities[$entity->getID()]); - } - - public function addTile(Tile $tile){ - $this->tiles[$tile->getID()] = $tile; - } - - public function removeTile(Tile $tile){ - unset($this->tiles[$tile->getID()]); - } - - public function getEntities(){ - return $this->entities; - } - - public function getTiles(){ - return $this->tiles; - } - - public function isLoaded(){ - return $this->getLevel() === null ? false : $this->getLevel()->isChunkLoaded($this->getX(), $this->getZ()); - } - public function load($generate = true){ return $this->getLevel() === null ? false : $this->getLevel()->getChunk($this->getX(), $this->getZ(), true) instanceof Chunk; } - public function unload($save = true, $safe = true){ - $level = $this->getLevel(); - if($level === null){ - return true; + public function getBlockIdArray(){ + $blocks = ""; + for($y = 0; $y < Chunk::SECTION_COUNT; ++$y){ + $blocks .= $this->sections[$y]->getIdArray(); } - if($save === true){ - $level->saveChunk($this->getX(), $this->getZ()); + return $blocks; + } + + public function getBlockDataArray(){ + $data = ""; + for($y = 0; $y < Chunk::SECTION_COUNT; ++$y){ + $data .= $this->sections[$y]->getDataArray(); } - if($this->getLevel()->unloadChunk($this->getX(), $this->getZ(), $safe)){ - foreach($this->getEntities() as $entity){ - $entity->close(); - } - foreach($this->getTiles() as $tile){ - $tile->close(); - } + return $data; + } + + public function getBlockSkyLightArray(){ + $skyLight = ""; + for($y = 0; $y < Chunk::SECTION_COUNT; ++$y){ + $skyLight .= $this->sections[$y]->getSkyLightArray(); } + return $skyLight; + } + + public function getBlockLightArray(){ + $blockLight = ""; + for($y = 0; $y < Chunk::SECTION_COUNT; ++$y){ + $blockLight .= $this->sections[$y]->getLightArray(); + } + return $blockLight; } /** @@ -325,12 +297,4 @@ abstract class BaseChunk implements Chunk{ return $this->sections; } - public function getBiomeIdArray(){ - return $this->biomeIds; - } - - public function getBiomeColorArray(){ - return $this->biomeColors; - } - } \ No newline at end of file diff --git a/src/pocketmine/level/format/generic/BaseFullChunk.php b/src/pocketmine/level/format/generic/BaseFullChunk.php new file mode 100644 index 000000000..6d4ae583b --- /dev/null +++ b/src/pocketmine/level/format/generic/BaseFullChunk.php @@ -0,0 +1,260 @@ + */ + protected $level; + + protected $x; + protected $z; + + /** + * @param LevelProvider $level + * @param int $x + * @param int $z + * @param string $blocks + * @param string $data + * @param string $skyLight + * @param string $blockLight + * @param string $biomeIds + * @param int[] $biomeColors + * @param Compound[] $entities + * @param Compound[] $tiles + * + * @throws \Exception + */ + protected function __construct(LevelProvider $level, $x, $z, $blocks, $data, $skyLight, $blockLight, $biomeIds = null, array $biomeColors = [], array $entities = [], array $tiles = []){ + $this->level = new \WeakRef($level); + $this->x = (int) $x; + $this->z = (int) $z; + + $this->blocks = $blocks; + $this->data = $data; + $this->skyLight = $skyLight; + $this->blockLight = $blockLight; + + if(strlen($biomeIds) === 256){ + $this->biomeIds = $biomeIds; + }else{ + $this->biomeIds = str_repeat("\x01", 256); + } + + if(count($biomeColors) === 256){ + $this->biomeColors = $biomeColors; + }else{ + $this->biomeColors = array_fill(0, 256, Binary::readInt("\x00\x85\xb2\x4a")); + } + + $this->getLevel()->getLevel()->timings->syncChunkLoadEntitiesTimer->startTiming(); + foreach($entities as $nbt){ + if($nbt instanceof Compound){ + if(!isset($nbt->id)){ + continue; + } + + if($nbt->id instanceof String){ //New format + switch($nbt["id"]){ + case "Item": + (new DroppedItem($this, $nbt))->spawnToAll(); + break; + } + }else{ //Old format + + } + } + } + $this->getLevel()->getLevel()->timings->syncChunkLoadEntitiesTimer->stopTiming(); + + $this->getLevel()->getLevel()->timings->syncChunkLoadTileEntitiesTimer->startTiming(); + foreach($tiles as $nbt){ + if($nbt instanceof Compound){ + if(!isset($nbt->id)){ + continue; + } + switch($nbt["id"]){ + case Tile::CHEST: + new Chest($this, $nbt); + break; + case Tile::FURNACE: + new Furnace($this, $nbt); + break; + case Tile::SIGN: + new Sign($this, $nbt); + break; + } + } + } + $this->getLevel()->getLevel()->timings->syncChunkLoadTileEntitiesTimer->stopTiming(); + } + + public function getX(){ + return $this->x; + } + + public function getZ(){ + return $this->z; + } + + /** + * @return LevelProvider + */ + public function getLevel(){ + return $this->level->valid() ? $this->level->get() : null; + } + + public function getBiomeId($x, $z){ + return ord($this->biomeIds{($z << 4) + $x}); + } + + public function setBiomeId($x, $z, $biomeId){ + $this->biomeIds{($z << 4) + $x} = chr($biomeId); + } + + public function getBiomeColor($x, $z){ + $color = $this->biomeColors[($z << 4) + $x] & 0xFFFFFF; + return [$color >> 16, ($color >> 8) & 0xFF, $color & 0xFF]; + } + + public function setBiomeColor($x, $z, $R, $G, $B){ + $this->biomeColors[($z << 4) + $x] = 0 | (($R & 0xFF) << 16) | (($G & 0xFF) << 8) | ($B & 0xFF); + } + + public function getHighestBlockAt($x, $z){ + $column = $this->getBlockIdColumn($x, $z); + for($y = 127; $y >= 0; --$y){ + if($column{$y} !== "\x00"){ + return $y; + } + } + + return 0; + } + + public function addEntity(Entity $entity){ + $this->entities[$entity->getID()] = $entity; + } + + public function removeEntity(Entity $entity){ + unset($this->entities[$entity->getID()]); + } + + public function addTile(Tile $tile){ + $this->tiles[$tile->getID()] = $tile; + } + + public function removeTile(Tile $tile){ + unset($this->tiles[$tile->getID()]); + } + + public function getEntities(){ + return $this->entities; + } + + public function getTiles(){ + return $this->tiles; + } + + public function isLoaded(){ + return $this->getLevel() === null ? false : $this->getLevel()->isChunkLoaded($this->getX(), $this->getZ()); + } + + public function load($generate = true){ + return $this->getLevel() === null ? false : $this->getLevel()->getChunk($this->getX(), $this->getZ(), true) instanceof FullChunk; + } + + public function unload($save = true, $safe = true){ + $level = $this->getLevel(); + if($level === null){ + return true; + } + if($save === true){ + $level->saveChunk($this->getX(), $this->getZ()); + } + if($this->getLevel()->unloadChunk($this->getX(), $this->getZ(), $safe)){ + foreach($this->getEntities() as $entity){ + $entity->close(); + } + foreach($this->getTiles() as $tile){ + $tile->close(); + } + } + } + + public function getBlockIdArray(){ + return $this->blocks; + } + + public function getBlockDataArray(){ + return $this->data; + } + + public function getBlockSkyLightArray(){ + return $this->skyLight; + } + + public function getBlockLightArray(){ + return $this->blockLight; + } + + public function getBiomeIdArray(){ + return $this->biomeIds; + } + + public function getBiomeColorArray(){ + return $this->biomeColors; + } + +} \ 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 c9cc1d553..49e8b1e58 100644 --- a/src/pocketmine/level/format/mcregion/Chunk.php +++ b/src/pocketmine/level/format/mcregion/Chunk.php @@ -21,8 +21,7 @@ namespace pocketmine\level\format\mcregion; -use pocketmine\level\format\generic\BaseChunk; -use pocketmine\level\format\generic\EmptyChunkSection; +use pocketmine\level\format\generic\BaseFullChunk; use pocketmine\level\format\LevelProvider; use pocketmine\nbt\NBT; use pocketmine\nbt\tag\Byte; @@ -32,7 +31,7 @@ use pocketmine\nbt\tag\Enum; use pocketmine\nbt\tag\IntArray; use pocketmine\utils\Binary; -class Chunk extends BaseChunk{ +class Chunk extends BaseFullChunk{ /** @var Compound */ protected $nbt; @@ -69,37 +68,131 @@ class Chunk extends BaseChunk{ $this->nbt->BiomeColors = new IntArray("BiomeColors", array_fill(0, 156, Binary::readInt("\x00\x85\xb2\x4a"))); } - /** @var ChunkSection[] $sections */ - $sections = []; + parent::__construct($level, $this->nbt["xPos"], $this->nbt["zPos"], $this->nbt["Blocks"], $this->nbt["Data"], $this->nbt["SkyLight"], $this->nbt["BLockLight"], $this->nbt->Biomes->getValue(), $this->nbt->BiomeColors->getValue(), $this->nbt->Entities->getValue(), $this->nbt->TileEntities->getValue()); + } - $blockLight = $skyLight = $datas = $blocks = [$fill = array_fill(0, 256, ""), $fill, $fill, $fill, $fill, $fill, $fill, $fill]; + public function getBlockId($x, $y, $z){ + return ord($this->blocks{($z << 11) + ($x << 7) + $y}); + } - $offset = 0; - for($i = 0; $i < 256; ++$i){ - list($blocks[0][$i], $blocks[1][$i], $blocks[2][$i], $blocks[3][$i], $blocks[4][$i], $blocks[5][$i], $blocks[6][$i], $blocks[7][$i]) = str_split(substr($this->nbt["Blocks"], $offset << 1, 128), 16); - list($datas[0][$i], $datas[1][$i], $datas[2][$i], $datas[3][$i], $datas[4][$i], $datas[5][$i], $datas[6][$i], $datas[7][$i]) = str_split(substr($this->nbt["Data"], $offset, 64), 8); - list($skyLight[0][$i], $skyLight[1][$i], $skyLight[2][$i], $skyLight[3][$i], $skyLight[4][$i], $skyLight[5][$i], $skyLight[6][$i], $skyLight[7][$i]) = str_split(substr($this->nbt["SkyLight"], $offset, 64), 8); - list($blockLight[0][$i], $blockLight[1][$i], $blockLight[2][$i], $blockLight[3][$i], $blockLight[4][$i], $blockLight[5][$i], $blockLight[6][$i], $blockLight[7][$i]) = str_split(substr($this->nbt["BlockLight"], $offset, 64), 8); - $offset += 64; + public function setBlockId($x, $y, $z, $id){ + $this->blocks{($z << 11) + ($x << 7) + $y} = chr($id); + } + + public function getBlockData($x, $y, $z){ + $m = ord($this->data{($z << 10) + ($x << 6) + ($y >> 1)}); + if(($y & 1) === 0){ + return $m >> 4; + }else{ + return $m & 0x0F; } + } - for($Y = 0; $Y < 8; ++$Y){ - $sections[$Y] = new ChunkSection( - $Y, - implode($blocks[$Y]), - implode($datas[$Y]), - implode($skyLight[$Y]), - implode($blockLight[$Y]) - ); + public function setBlockData($x, $y, $z, $data){ + $i = ($z << 10) + ($x << 6) + ($y >> 1); + $old_m = ord($this->data{$i}); + if(($y & 1) === 0){ + $this->data{$i} = chr((($data & 0x0f) << 4) | ($old_m & 0x0f)); + }else{ + $this->data{$i} = chr(($old_m & 0xf0) | ($data & 0x0f)); } + } - for($y = 0; $y < 8; ++$y){ - if(substr_count($sections[$y]->getIdArray(), "\x00") === 4096){ - $sections[$y] = new EmptyChunkSection($y); + public function getBlock($x, $y, $z, &$blockId, &$meta = null){ + $i = ($z << 11) + ($x << 7) + $y; + $blockId = ord($this->blocks{$i}); + $m = ord($this->data{$i >> 1}); + if(($y & 1) === 0){ + $meta = $m >> 4; + }else{ + $meta = $m & 0x0F; + } + } + + public function setBlock($x, $y, $z, $blockId = null, $meta = null){ + $i = ($z << 11) + ($x << 7) + $y; + + $changed = false; + + if($blockId !== null){ + $blockId = chr($blockId); + if($this->blocks{$i} !== $blockId){ + $this->blocks{$i} = $blockId; + $changed = true; } } - parent::__construct($level, $this->nbt["xPos"], $this->nbt["zPos"], $sections, $this->nbt->Biomes->getValue(), $this->nbt->BiomeColors->getValue(), $this->nbt->Entities->getValue(), $this->nbt->TileEntities->getValue()); + if($meta !== null){ + $i >>= 1; + $old_m = ord($this->data{$i}); + if(($y & 1) === 0){ + $this->data{$i} = chr((($meta & 0x0f) << 4) | ($old_m & 0x0f)); + if((($old_m & 0xf0) >> 4) !== $meta){ + $changed = true; + } + }else{ + $this->data{$i} = chr(($old_m & 0xf0) | ($meta & 0x0f)); + if(($old_m & 0x0f) !== $meta){ + $changed = true; + } + } + } + + return $changed; + } + + public function getBlockSkyLight($x, $y, $z){ + $sl = ord($this->skyLight{($z << 10) + ($x << 6) + ($y >> 1)}); + if(($y & 1) === 0){ + return $sl >> 4; + }else{ + return $sl & 0x0F; + } + } + + public function setBlockSkyLight($x, $y, $z, $level){ + $i = ($z << 10) + ($x << 6) + ($y >> 1); + $old_sl = ord($this->skyLight{$i}); + if(($y & 1) === 0){ + $this->skyLight{$i} = chr((($level & 0x0f) << 4) | ($old_sl & 0x0f)); + }else{ + $this->skyLight{$i} = chr(($old_sl & 0xf0) | ($level & 0x0f)); + } + } + + public function getBlockLight($x, $y, $z){ + $l = ord($this->blockLight{($z << 10) + ($x << 6) + ($y >> 1)}); + if(($y & 1) === 0){ + return $l >> 4; + }else{ + return $l & 0x0F; + } + } + + public function setBlockLight($x, $y, $z, $level){ + $i = ($z << 10) + ($x << 6) + ($y >> 1); + $old_l = ord($this->blockLight{$i}); + if(($y & 1) === 0){ + $this->blockLight{$i} = chr((($level & 0x0f) << 4) | ($old_l & 0x0f)); + }else{ + $this->blockLight{$i} = chr(($old_l & 0xf0) | ($level & 0x0f)); + } + } + + public function getBlockIdColumn($x, $z){ + return substr($this->blocks, ($z << 11) + ($x << 7), 128); + } + + public function getBlockDataColumn($x, $z){ + return substr($this->data, ($z << 10) + ($x << 6), 64); + } + + public function getBlockSkyLightColumn($x, $z){ + return substr($this->skyLight, ($z << 10) + ($x << 6), 64); + } + + public function getBlockLightColumn($x, $z){ + return substr($this->blockLight, ($z << 10) + ($x << 6), 64); } /** @@ -117,34 +210,12 @@ class Chunk extends BaseChunk{ } public function getChunkSnapshot($includeMaxBlockY = true, $includeBiome = false, $includeBiomeTemp = false){ - $blockId = ""; - $blockData = ""; - $blockSkyLight = ""; - $blockLight = ""; $emptySections = [false, false, false, false, false, false, false, false]; - $emptyBlocks = str_repeat("\x00", 4096); - $emptyHalf = str_repeat("\x00", 2048); - - foreach($this->sections as $i => $section){ - if($section instanceof EmptyChunkSection){ - $blockId .= $emptyBlocks; - $blockData .= $emptyHalf; - $blockSkyLight .= $emptyHalf; - $blockLight .= $emptyHalf; - $emptySections[$i] = true; - }else{ - $blockId .= $section->getIdArray(); - $blockData .= $section->getDataArray(); - $blockSkyLight .= $section->getSkyLightArray(); - $blockLight .= $section->getLightArray(); - } - } - //TODO: maxBlockY, biomeMap, biomeTemp //TODO: time - return new ChunkSnapshot($this->getX(), $this->getZ(), $this->getLevel()->getName(), 0 /*$this->getLevel()->getTime()*/, $blockId, $blockData, $blockSkyLight, $blockLight, $emptySections, null, null, null, null); + return new ChunkSnapshot($this->getX(), $this->getZ(), $this->getLevel()->getName(), 0 /*$this->getLevel()->getTime()*/, $this->getBlockIdArray(), $this->getBlockDataArray(), $this->getBlockSkyLightArray(), $this->getBlockLightArray(), $emptySections, null, null, null, null); } /** diff --git a/src/pocketmine/level/format/mcregion/ChunkSection.php b/src/pocketmine/level/format/mcregion/ChunkSection.php deleted file mode 100644 index d15979738..000000000 --- a/src/pocketmine/level/format/mcregion/ChunkSection.php +++ /dev/null @@ -1,184 +0,0 @@ -y = $y; - $this->blocks = $blocks; - $this->data = $data; - $this->blockLight = $blockLight; - $this->skyLight = $skyLight; - } - - public function getY(){ - return $this->y; - } - - public function getBlockId($x, $y, $z){ - return ord($this->blocks{($z << 8) + ($x << 4) + $y}); - } - - public function setBlockId($x, $y, $z, $id){ - $this->blocks{($z << 8) + ($x << 4) + $y} = chr($id); - } - - public function getBlockData($x, $y, $z){ - $m = ord($this->data{($z << 7) + ($x << 3) + ($y >> 1)}); - if(($y & 1) === 0){ - return $m >> 4; - }else{ - return $m & 0x0F; - } - } - - public function setBlockData($x, $y, $z, $data){ - $i = ($z << 7) + ($x << 3) + ($y >> 1); - $old_m = ord($this->data{$i}); - if(($y & 1) === 0){ - $this->data{$i} = chr((($data & 0x0f) << 4) | ($old_m & 0x0f)); - }else{ - $this->data{$i} = chr(($old_m & 0xf0) | ($data & 0x0f)); - } - } - - public function getBlock($x, $y, $z, &$blockId, &$meta = null){ - $i = ($z << 8) + ($x << 4) + $y; - $blockId = ord($this->blocks{$i}); - $m = ord($this->data{$i >> 1}); - if(($y & 1) === 0){ - $meta = $m >> 4; - }else{ - $meta = $m & 0x0F; - } - } - - public function setBlock($x, $y, $z, $blockId = null, $meta = null){ - $i = ($z << 8) + ($x << 4) + $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((($meta & 0x0f) << 4) | ($old_m & 0x0f)); - if((($old_m & 0xf0) >> 4) !== $meta){ - $changed = true; - } - }else{ - $this->data{$i} = chr(($old_m & 0xf0) | ($meta & 0x0f)); - if(($old_m & 0x0f) !== $meta){ - $changed = true; - } - } - } - - return $changed; - } - - public function getBlockSkyLight($x, $y, $z){ - $sl = ord($this->skyLight{($z << 7) + ($x << 3) + ($y >> 1)}); - if(($y & 1) === 0){ - return $sl >> 4; - }else{ - return $sl & 0x0F; - } - } - - public function setBlockSkyLight($x, $y, $z, $level){ - $i = ($z << 7) + ($x << 3) + ($y >> 1); - $old_sl = ord($this->skyLight{$i}); - if(($y & 1) === 0){ - $this->skyLight{$i} = chr((($level & 0x0f) << 4) | ($old_sl & 0x0f)); - }else{ - $this->skyLight{$i} = chr(($old_sl & 0xf0) | ($level & 0x0f)); - } - } - - public function getBlockLight($x, $y, $z){ - $l = ord($this->blockLight{($z << 7) + ($x << 3) + ($y >> 1)}); - if(($y & 1) === 0){ - return $l >> 4; - }else{ - return $l & 0x0F; - } - } - - public function setBlockLight($x, $y, $z, $level){ - $i = ($z << 7) + ($x << 3) + ($y >> 1); - $old_l = ord($this->blockLight{$i}); - if(($y & 1) === 0){ - $this->blockLight{$i} = chr((($level & 0x0f) << 4) | ($old_l & 0x0f)); - }else{ - $this->blockLight{$i} = chr(($old_l & 0xf0) | ($level & 0x0f)); - } - } - - public function getBlockIdColumn($x, $z){ - return substr($this->blocks, ($z << 8) + ($x << 4), 16); - } - - public function getBlockDataColumn($x, $z){ - return substr($this->data, ($z << 7) + ($x << 3), 8); - } - - public function getBlockSkyLightColumn($x, $z){ - return substr($this->skyLight, ($z << 7) + ($x << 3), 8); - } - - public function getBlockLightColumn($x, $z){ - return substr($this->blockLight, ($z << 7) + ($x << 3), 8); - } - - public function getIdArray(){ - return $this->blocks; - } - - public function getDataArray(){ - return $this->data; - } - - public function getSkyLightArray(){ - return $this->skyLight; - } - - public function getLightArray(){ - return $this->blockLight; - } - -} \ No newline at end of file diff --git a/src/pocketmine/level/format/mcregion/McRegion.php b/src/pocketmine/level/format/mcregion/McRegion.php index 098cdf5b1..2a6de7a62 100644 --- a/src/pocketmine/level/format/mcregion/McRegion.php +++ b/src/pocketmine/level/format/mcregion/McRegion.php @@ -45,6 +45,14 @@ class McRegion extends BaseLevelProvider{ return "mcregion"; } + public static function getProviderOrder(){ + return self::ORDER_ZXY; + } + + public static function usesChunkSection(){ + return false; + } + public static function isValid($path){ $isValid = (file_exists($path . "/level.dat") and is_dir($path . "/region/")); diff --git a/src/pocketmine/level/format/mcregion/RegionLoader.php b/src/pocketmine/level/format/mcregion/RegionLoader.php index bfaceed8d..9fe5c70c1 100644 --- a/src/pocketmine/level/format/mcregion/RegionLoader.php +++ b/src/pocketmine/level/format/mcregion/RegionLoader.php @@ -187,32 +187,12 @@ class RegionLoader{ } public function writeChunk(Chunk $chunk){ - //TODO $nbt = $chunk->getNBT(); - $nbt->Sections = new Enum("Sections", []); - $nbt->Sections->setTagType(NBT::TAG_Compound); - $blocks = ""; - $data = ""; - $skyLight = ""; - $blockLight = ""; - $sections = $chunk->getSections(); - - for($i = 0; $i < 256; ++$i){ - $x = $i & 0x0F; - $z = $i >> 4; - for($s = 0; $s < 0; ++$s){ - $blocks .= $sections[$s]->getBlockIdColumn($x, $z); - $data .= $sections[$s]->getBlockDataColumn($x, $z); - $skyLight .= $sections[$s]->getBlockSkyLightColumn($x, $z); - $blockLight .= $sections[$s]->getBlockLightColumn($x, $z); - } - } - - $nbt->Blocks = new ByteArray("Blocks", $blocks); - $nbt->Data = new ByteArray("Data", $data); - $nbt->SkyLight = new ByteArray("SkyLight", $skyLight); - $nbt->BlockLight = new ByteArray("BlockLight", $blockLight); + $nbt->Blocks = new ByteArray("Blocks", $chunk->getBlockIdArray()); + $nbt->Data = new ByteArray("Data", $chunk->getBlockDataArray()); + $nbt->SkyLight = new ByteArray("SkyLight", $chunk->getBlockSkyLightArray()); + $nbt->BlockLight = new ByteArray("BlockLight", $chunk->getBlockLightArray()); $nbt->Biomes = new ByteArray("Biomes", $chunk->getBiomeIdArray()); $nbt->BiomeColors = new IntArray("BiomeColors", $chunk->getBiomeColorArray()); From 4128b22e85abdcecdcb35f3ff1d349700763b697 Mon Sep 17 00:00:00 2001 From: Shoghi Cervantes Date: Wed, 30 Jul 2014 16:38:56 +0200 Subject: [PATCH 07/13] Reuse McRegion classes on Anvil --- src/pocketmine/level/format/anvil/Anvil.php | 144 +------------ .../level/format/anvil/RegionLoader.php | 193 +----------------- .../level/format/mcregion/RegionLoader.php | 6 +- 3 files changed, 10 insertions(+), 333 deletions(-) diff --git a/src/pocketmine/level/format/anvil/Anvil.php b/src/pocketmine/level/format/anvil/Anvil.php index 349247bff..b21720122 100644 --- a/src/pocketmine/level/format/anvil/Anvil.php +++ b/src/pocketmine/level/format/anvil/Anvil.php @@ -22,6 +22,7 @@ namespace pocketmine\level\format\anvil; use pocketmine\level\format\generic\BaseLevelProvider; +use pocketmine\level\format\mcregion\McRegion; use pocketmine\level\format\SimpleChunk; use pocketmine\level\generator\Generator; use pocketmine\level\Level; @@ -34,7 +35,7 @@ use pocketmine\nbt\tag\Long; use pocketmine\nbt\tag\String; use pocketmine\Player; -class Anvil extends BaseLevelProvider{ +class Anvil extends McRegion{ /** @var RegionLoader[] */ protected $regions = []; @@ -69,68 +70,6 @@ class Anvil extends BaseLevelProvider{ return $isValid; } - public static function generate($path, $name, $seed, $generator, array $options = []){ - @mkdir($path, 0777, true); - @mkdir($path . "/region", 0777); - //TODO, add extra details - $levelData = new Compound("Data", [ - "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::BIG_ENDIAN); - $nbt->setData(new Compound(null, [ - "Data" => $levelData - ])); - $buffer = $nbt->writeCompressed(); - @file_put_contents($path . "level.dat", $buffer); - } - - public static function getRegionIndex($chunkX, $chunkZ, &$x, &$z){ - $x = $chunkX >> 5; - $z = $chunkZ >> 5; - } - - public function unloadChunks(){ - $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){ $index = Level::chunkHash($chunkX, $chunkZ); if(isset($this->chunks[$index])){ @@ -151,54 +90,6 @@ class Anvil extends BaseLevelProvider{ } } - public function unloadChunk($x, $z, $safe = true){ - $chunk = $this->getChunk($x, $z, false); - if($chunk instanceof Chunk){ - if($safe === true and $this->isChunkLoaded($x, $z)){ - foreach($chunk->getEntities() as $entity){ - if($entity instanceof Player){ - return false; - } - } - } - - foreach($chunk->getEntities() as $entity){ - $entity->close(); - } - - foreach($chunk->getTiles() as $tile){ - $tile->close(); - } - - $this->chunks[$index = Level::chunkHash($x, $z)] = null; - - unset($this->chunks[$index]); - } - return true; - } - - public function saveChunk($x, $z){ - if($this->isChunkLoaded($x, $z)){ - $this->getRegion($x >> 5, $z >> 5)->writeChunk($this->getChunk($x, $z)); - - return true; - } - - return false; - } - - /** - * @param $x - * @param $z - * - * @return RegionLoader - */ - protected function getRegion($x, $z){ - $index = $x . ":" . $z; - - return isset($this->regions[$index]) ? $this->regions[$index] : null; - } - /** * @param int $chunkX * @param int $chunkZ @@ -207,13 +98,7 @@ class Anvil extends BaseLevelProvider{ * @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 parent::getChunk($chunkX, $chunkZ, $create); } public function setChunk($chunkX, $chunkZ, SimpleChunk $chunk){ @@ -249,9 +134,9 @@ class Anvil extends BaseLevelProvider{ return new ChunkSection(new Compound(null, [ "Y" => new Byte("Y", $Y), "Blocks" => new ByteArray("Blocks", str_repeat("\xff", 4096)), - "Data" => new ByteArray("Data", str_repeat("\xff", 2048)), - "SkyLight" => new ByteArray("SkyLight", str_repeat("\xff", 2048)), //TODO - "BlockLight" => new ByteArray("BlockLight", str_repeat("\x00", 2048)) //TODO + "Data" => new ByteArray("Data", $half = str_repeat("\xff", 2048)), + "SkyLight" => new ByteArray("SkyLight", $half), + "BlockLight" => new ByteArray("BlockLight", $half) ])); } @@ -263,15 +148,6 @@ class Anvil extends BaseLevelProvider{ return false; } - public function isChunkPopulated($chunkX, $chunkZ){ - $chunk = $this->getChunk($chunkX, $chunkZ); - if($chunk instanceof Chunk){ - return $chunk->isPopulated(); - }else{ - return false; - } - } - protected function loadRegion($x, $z){ $index = $x . ":" . $z; if(isset($this->regions[$index])){ @@ -282,12 +158,4 @@ class Anvil extends BaseLevelProvider{ return true; } - - public function close(){ - $this->unloadChunks(); - foreach($this->regions as $index => $region){ - $region->close(); - unset($this->regions[$index]); - } - } } \ No newline at end of file diff --git a/src/pocketmine/level/format/anvil/RegionLoader.php b/src/pocketmine/level/format/anvil/RegionLoader.php index 0c7885eef..b465e1be0 100644 --- a/src/pocketmine/level/format/anvil/RegionLoader.php +++ b/src/pocketmine/level/format/anvil/RegionLoader.php @@ -33,21 +33,12 @@ use pocketmine\nbt\tag\Long; use pocketmine\Player; use pocketmine\utils\Binary; -class RegionLoader{ +class RegionLoader extends \pocketmine\level\format\mcregion\RegionLoader{ const VERSION = 1; const COMPRESSION_GZIP = 1; const COMPRESSION_ZLIB = 2; public static $COMPRESSION_LEVEL = 7; - protected $x; - protected $z; - protected $filePath; - protected $filePointer; - protected $lastSector; - /** @var LevelProvider */ - protected $levelProvider; - protected $locationTable = []; - public function __construct(LevelProvider $level, $regionX, $regionZ){ $this->x = $regionX; $this->z = $regionZ; @@ -65,19 +56,6 @@ class RegionLoader{ } } - public function __destruct(){ - if(is_resource($this->filePointer)){ - $this->cleanGarbage(); - $this->writeLocationTable(); - flock($this->filePointer, LOCK_UN); - fclose($this->filePointer); - } - } - - protected function isChunkGenerated($index){ - return !($this->locationTable[$index][0] === 0 or $this->locationTable[$index][1] === 0); - } - public function readChunk($x, $z, $generate = true){ $index = self::getChunkOffset($x, $z); if($index < 0 or $index >= 4096){ @@ -131,10 +109,6 @@ class RegionLoader{ return new Chunk($this->levelProvider, $chunk->Level); } - public function chunkExists($x, $z){ - return $this->isChunkGenerated(self::getChunkOffset($x, $z)); - } - public function generateChunk($x, $z){ $nbt = new Compound("Level", []); $nbt->xPos = new Int("xPos", ($this->getX() * 32) + $x); @@ -158,30 +132,6 @@ class RegionLoader{ $this->saveChunk($x, $z, $nbt); } - protected function saveChunk($x, $z, Compound $nbt){ - $writer = new NBT(NBT::BIG_ENDIAN); - $nbt->setName("Level"); - $writer->setData(new Compound("", array("Level" => $nbt))); - $chunkData = $writer->writeCompressed(ZLIB_ENCODING_DEFLATE, self::$COMPRESSION_LEVEL); - $length = strlen($chunkData) + 1; - $sectors = (int) ceil(($length + 4) / 4096); - $index = self::getChunkOffset($x, $z); - if($this->locationTable[$index][1] < $sectors){ - $this->locationTable[$index][0] = $this->lastSector += $sectors; //The GC will clean this shift later - } - $this->locationTable[$index][1] = $sectors; - - fseek($this->filePointer, $this->locationTable[$index][0] << 12); - fwrite($this->filePointer, str_pad(Binary::writeInt($length) . chr(self::COMPRESSION_ZLIB) . $chunkData, $sectors << 12, "\x00", STR_PAD_RIGHT)); - $this->writeLocationIndex($index); - } - - public function removeChunk($x, $z){ - $index = self::getChunkOffset($x, $z); - $this->locationTable[$index][0] = 0; - $this->locationTable[$index][1] = 0; - } - public function writeChunk(Chunk $chunk){ $nbt = $chunk->getNBT(); $nbt->Sections = new Enum("Sections", []); @@ -226,145 +176,4 @@ class RegionLoader{ $this->saveChunk($chunk->getX() - ($this->getX() * 32), $chunk->getZ() - ($this->getZ() * 32), $nbt); } - protected static function getChunkOffset($x, $z){ - return $x + ($z << 5); - } - - public function close(){ - $this->writeLocationTable(); - flock($this->filePointer, LOCK_UN); - fclose($this->filePointer); - } - - public function doSlowCleanUp(){ - for($i = 0; $i < 1024; ++$i){ - if($this->locationTable[$i][0] === 0 or $this->locationTable[$i][1] === 0){ - continue; - } - fseek($this->filePointer, $this->locationTable[$i][0] << 12); - $chunk = fread($this->filePointer, $this->locationTable[$i][1] << 12); - $length = Binary::readInt(substr($chunk, 0, 4)); - if($length <= 1){ - $this->locationTable[$i] = array(0, 0, 0); //Non-generated chunk, remove it from index - } - $chunk = zlib_decode(substr($chunk, 5)); - if(strlen($chunk) <= 1){ - $this->locationTable[$i] = array(0, 0, 0); //Corrupted chunk, remove it - continue; - } - $chunk = chr(self::COMPRESSION_ZLIB) . zlib_encode($chunk, 15, 9); - $chunk = Binary::writeInt(strlen($chunk)) . $chunk; - $sectors = (int) ceil(strlen($chunk) / 4096); - if($sectors > $this->locationTable[$i][1]){ - $this->locationTable[$i][0] = $this->lastSector += $sectors; - } - fseek($this->filePointer, $this->locationTable[$i][0] << 12); - fwrite($this->filePointer, str_pad($chunk, $sectors << 12, "\x00", STR_PAD_RIGHT)); - } - $this->writeLocationTable(); - $n = $this->cleanGarbage(); - $this->writeLocationTable(); - - return $n; - } - - private function cleanGarbage(){ - $sectors = []; - foreach($this->locationTable as $index => $data){ //Calculate file usage - if($data[0] === 0 or $data[1] === 0){ - $this->locationTable[$index] = array(0, 0, 0); - continue; - } - for($i = 0; $i < $data[1]; ++$i){ - $sectors[$data[0]] = $index; - } - } - - if(count($sectors) === ($this->lastSector - 2)){ //No collection needed - return 0; - } - - ksort($sectors); - $shift = 0; - $lastSector = 1; //First chunk - 1 - - fseek($this->filePointer, 8192); - $sector = 2; - foreach($sectors as $sector => $index){ - if(($sector - $lastSector) > 1){ - $shift += $sector - $lastSector - 1; - } - if($shift > 0){ - fseek($this->filePointer, $sector << 12); - $old = fread($this->filePointer, 4096); - fseek($this->filePointer, ($sector - $shift) << 12); - fwrite($this->filePointer, $old, 4096); - } - $this->locationTable[$index][0] -= $shift; - $lastSector = $sector; - } - ftruncate($this->filePointer, ($sector + 1) << 12); //Truncate to the end of file written - return $shift; - } - - private function loadLocationTable(){ - fseek($this->filePointer, 0); - $this->lastSector = 1; - $table = fread($this->filePointer, 4 * 1024 * 2); - for($i = 0; $i < 1024; ++$i){ - $index = Binary::readInt(substr($table, $i << 2, 4)); - $this->locationTable[$i] = array(($index & ~0xff) >> 8, $index & 0xff, Binary::readInt(substr($table, 4096 + ($i << 2), 4))); - if(($this->locationTable[$i][0] + $this->locationTable[$i][1] - 1) > $this->lastSector){ - $this->lastSector = $this->locationTable[$i][0] + $this->locationTable[$i][1] - 1; - } - } - } - - private function writeLocationTable(){ - $table = ""; - - for($i = 0; $i < 1024; ++$i){ - $table .= Binary::writeInt(($this->locationTable[$i][0] << 8) | $this->locationTable[$i][1]); - } - for($i = 0; $i < 1024; ++$i){ - $table .= Binary::writeInt($this->locationTable[$i][2]); - } - fseek($this->filePointer, 0); - fwrite($this->filePointer, $table, 4096 * 2); - } - - private function writeLocationIndex($index){ - fseek($this->filePointer, $index << 2); - fwrite($this->filePointer, Binary::writeInt(($this->locationTable[$index][0] << 8) | $this->locationTable[$index][1]), 4); - fseek($this->filePointer, 4096 + ($index << 2)); - fwrite($this->filePointer, Binary::writeInt($this->locationTable[$index][2]), 4); - } - - private function createBlank(){ - fseek($this->filePointer, 0); - ftruncate($this->filePointer, 0); - $this->lastSector = 1; - $table = ""; - for($i = 0; $i < 1024; ++$i){ - $this->locationTable[$i] = array(0, 0); - $table .= Binary::writeInt(0); - } - - $time = time(); - for($i = 0; $i < 1024; ++$i){ - $this->locationTable[$i][2] = $time; - $table .= Binary::writeInt($time); - } - - fwrite($this->filePointer, $table, 4096 * 2); - } - - public function getX(){ - return $this->x; - } - - public function getZ(){ - return $this->z; - } - } \ No newline at end of file diff --git a/src/pocketmine/level/format/mcregion/RegionLoader.php b/src/pocketmine/level/format/mcregion/RegionLoader.php index 9fe5c70c1..4021574ba 100644 --- a/src/pocketmine/level/format/mcregion/RegionLoader.php +++ b/src/pocketmine/level/format/mcregion/RegionLoader.php @@ -305,7 +305,7 @@ class RegionLoader{ return $shift; } - private function loadLocationTable(){ + protected function loadLocationTable(){ fseek($this->filePointer, 0); $this->lastSector = 1; $table = fread($this->filePointer, 4 * 1024 * 2); @@ -331,14 +331,14 @@ class RegionLoader{ fwrite($this->filePointer, $table, 4096 * 2); } - private function writeLocationIndex($index){ + protected function writeLocationIndex($index){ fseek($this->filePointer, $index << 2); fwrite($this->filePointer, Binary::writeInt(($this->locationTable[$index][0] << 8) | $this->locationTable[$index][1]), 4); fseek($this->filePointer, 4096 + ($index << 2)); fwrite($this->filePointer, Binary::writeInt($this->locationTable[$index][2]), 4); } - private function createBlank(){ + protected function createBlank(){ fseek($this->filePointer, 0); ftruncate($this->filePointer, 0); $this->lastSector = 1; From 293102d8c40ad80b4184b0cfa68ab2a0a756d8ae Mon Sep 17 00:00:00 2001 From: Shoghi Cervantes Date: Wed, 30 Jul 2014 16:59:52 +0200 Subject: [PATCH 08/13] Fixed typo, BLockLight -> BlockLight --- src/pocketmine/level/format/mcregion/Chunk.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pocketmine/level/format/mcregion/Chunk.php b/src/pocketmine/level/format/mcregion/Chunk.php index 49e8b1e58..52fe2ddff 100644 --- a/src/pocketmine/level/format/mcregion/Chunk.php +++ b/src/pocketmine/level/format/mcregion/Chunk.php @@ -68,7 +68,7 @@ class Chunk extends BaseFullChunk{ $this->nbt->BiomeColors = new IntArray("BiomeColors", array_fill(0, 156, Binary::readInt("\x00\x85\xb2\x4a"))); } - parent::__construct($level, $this->nbt["xPos"], $this->nbt["zPos"], $this->nbt["Blocks"], $this->nbt["Data"], $this->nbt["SkyLight"], $this->nbt["BLockLight"], $this->nbt->Biomes->getValue(), $this->nbt->BiomeColors->getValue(), $this->nbt->Entities->getValue(), $this->nbt->TileEntities->getValue()); + parent::__construct($level, $this->nbt["xPos"], $this->nbt["zPos"], $this->nbt["Blocks"], $this->nbt["Data"], $this->nbt["SkyLight"], $this->nbt["BlockLight"], $this->nbt->Biomes->getValue(), $this->nbt->BiomeColors->getValue(), $this->nbt->Entities->getValue(), $this->nbt->TileEntities->getValue()); } public function getBlockId($x, $y, $z){ From 765d4f30c74c739bcb7443395c7310eef56f5393 Mon Sep 17 00:00:00 2001 From: Shoghi Cervantes Date: Wed, 30 Jul 2014 17:30:22 +0200 Subject: [PATCH 09/13] Added caching to non-converted network chunks --- src/pocketmine/level/Level.php | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/pocketmine/level/Level.php b/src/pocketmine/level/Level.php index f80021cbd..2fb978586 100644 --- a/src/pocketmine/level/Level.php +++ b/src/pocketmine/level/Level.php @@ -1414,6 +1414,11 @@ class Level implements ChunkManager, Metadatable{ } protected function getNetworkChunk($x, $z){ + $index = Level::chunkHash($x, $z); + if(ADVANCED_CACHE == true and ($cache = Cache::get("world:".$this->getID().":" . $index)) !== false){ + return $cache; + } + $chunk = $this->getChunkAt($x, $z, true); $tiles = ""; $nbt = new NBT(NBT::LITTLE_ENDIAN); @@ -1429,7 +1434,12 @@ class Level implements ChunkManager, Metadatable{ $biomeColors .= Binary::writeInt($color); } - return 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); + $encoded = 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); + if(ADVANCED_CACHE == true){ + Cache::add("world:".$this->getID().":" . $index, $encoded); + } + + return $encoded; } protected function processChunkRequest(){ From 1a442b793cdf65578f9ab961d2583688760b0d66 Mon Sep 17 00:00:00 2001 From: Shoghi Cervantes Date: Wed, 30 Jul 2014 18:24:59 +0200 Subject: [PATCH 10/13] Fixed Tiles/Entities not using the correct chunk type --- src/pocketmine/entity/Entity.php | 7 ++++--- src/pocketmine/level/Level.php | 13 ++++++++++--- src/pocketmine/tile/Chest.php | 3 ++- src/pocketmine/tile/Furnace.php | 3 ++- src/pocketmine/tile/Sign.php | 3 ++- src/pocketmine/tile/Spawnable.php | 3 ++- src/pocketmine/tile/Tile.php | 3 ++- 7 files changed, 24 insertions(+), 11 deletions(-) diff --git a/src/pocketmine/entity/Entity.php b/src/pocketmine/entity/Entity.php index a6ae3d7e9..4fe825b87 100644 --- a/src/pocketmine/entity/Entity.php +++ b/src/pocketmine/entity/Entity.php @@ -35,6 +35,7 @@ use pocketmine\event\entity\EntitySpawnEvent; use pocketmine\event\entity\EntityTeleportEvent; use pocketmine\event\Timings; use pocketmine\level\format\Chunk; +use pocketmine\level\format\FullChunk; use pocketmine\level\Level; use pocketmine\level\Position; use pocketmine\math\AxisAlignedBB; @@ -146,7 +147,7 @@ abstract class Entity extends Position implements Metadatable{ public $closed = false; - public function __construct(Chunk $chunk, Compound $nbt){ + public function __construct(FullChunk $chunk, Compound $nbt){ if($chunk->getLevel() === null){ throw new \Exception("Invalid garbage Chunk given to Entity"); } @@ -932,7 +933,7 @@ abstract class Entity extends Position implements Metadatable{ if($this->chunk === null or ($this->chunk->getX() !== ($this->x >> 4) and $this->chunk->getZ() !== ($this->z >> 4))){ - if($this->chunk instanceof Chunk){ + if($this->chunk instanceof FullChunk){ $this->chunk->removeEntity($this); } $this->getLevel()->loadChunk($this->x >> 4, $this->z >> 4); @@ -1040,7 +1041,7 @@ abstract class Entity extends Position implements Metadatable{ if($this->closed === false){ $this->closed = true; unset(Entity::$needUpdate[$this->id]); - if($this->chunk instanceof Chunk){ + if($this->chunk instanceof FullChunk){ $this->chunk->removeEntity($this); } $this->getLevel()->removeEntity($this); diff --git a/src/pocketmine/level/Level.php b/src/pocketmine/level/Level.php index 2fb978586..216776db0 100644 --- a/src/pocketmine/level/Level.php +++ b/src/pocketmine/level/Level.php @@ -37,6 +37,7 @@ use pocketmine\event\LevelTimings; use pocketmine\event\player\PlayerInteractEvent; use pocketmine\item\Item; use pocketmine\level\format\Chunk; +use pocketmine\level\format\FullChunk; use pocketmine\level\format\generic\EmptyChunkSection; use pocketmine\level\format\LevelProvider; use pocketmine\level\format\SimpleChunk; @@ -1309,6 +1310,12 @@ class Level implements ChunkManager, Metadatable{ } $blockIds = []; $data = []; + + if(!$this->useSections){ + //TODO + return new SimpleChunk($x, $z, 0); + } + for($Y = 0; $Y < 8; ++$Y){ $section = $chunk->getSection($Y); $blockIds[$Y] = $section->getIdArray(); @@ -1573,13 +1580,13 @@ class Level implements ChunkManager, Metadatable{ */ public function loadChunk($x, $z, $generate = true){ if($generate === true){ - return $this->getChunkAt($x, $z, true) instanceof Chunk; + return $this->getChunkAt($x, $z, true) instanceof FullChunk; } $this->cancelUnloadChunkRequest($x, $z); $chunk = $this->provider->getChunk($x, $z, false); - if($chunk instanceof Chunk){ + if($chunk instanceof FullChunk){ $this->chunks[Level::chunkHash($x, $z)] = $chunk; return true; }else{ @@ -1587,7 +1594,7 @@ class Level implements ChunkManager, Metadatable{ $this->provider->loadChunk($x, $z); $this->timings->syncChunkLoadTimer->stopTiming(); - if(($chunk = $this->provider->getChunk($x, $z)) instanceof Chunk){ + if(($chunk = $this->provider->getChunk($x, $z)) instanceof FullChunk){ $this->chunks[Level::chunkHash($x, $z)] = $chunk; return true; } diff --git a/src/pocketmine/tile/Chest.php b/src/pocketmine/tile/Chest.php index 569188933..45af65308 100644 --- a/src/pocketmine/tile/Chest.php +++ b/src/pocketmine/tile/Chest.php @@ -26,6 +26,7 @@ use pocketmine\inventory\DoubleChestInventory; use pocketmine\inventory\InventoryHolder; use pocketmine\item\Item; use pocketmine\level\format\Chunk; +use pocketmine\level\format\FullChunk; use pocketmine\math\Vector3 as Vector3; use pocketmine\nbt\NBT; use pocketmine\nbt\tag\Byte; @@ -42,7 +43,7 @@ class Chest extends Spawnable implements InventoryHolder, Container{ /** @var DoubleChestInventory */ protected $doubleInventory = null; - public function __construct(Chunk $chunk, Compound $nbt){ + public function __construct(FullChunk $chunk, Compound $nbt){ $nbt["id"] = Tile::CHEST; parent::__construct($chunk, $nbt); $this->inventory = new ChestInventory($this); diff --git a/src/pocketmine/tile/Furnace.php b/src/pocketmine/tile/Furnace.php index 36d2faa3e..43d212010 100644 --- a/src/pocketmine/tile/Furnace.php +++ b/src/pocketmine/tile/Furnace.php @@ -27,6 +27,7 @@ use pocketmine\inventory\FurnaceRecipe; use pocketmine\inventory\InventoryHolder; use pocketmine\item\Item; use pocketmine\level\format\Chunk; +use pocketmine\level\format\FullChunk; use pocketmine\nbt\NBT; use pocketmine\nbt\tag\Byte; use pocketmine\nbt\tag\Compound; @@ -37,7 +38,7 @@ class Furnace extends Tile implements InventoryHolder, Container{ /** @var FurnaceInventory */ protected $inventory; - public function __construct(Chunk $chunk, Compound $nbt){ + public function __construct(FullChunk $chunk, Compound $nbt){ $nbt["id"] = Tile::FURNACE; parent::__construct($chunk, $nbt); $this->inventory = new FurnaceInventory($this); diff --git a/src/pocketmine/tile/Sign.php b/src/pocketmine/tile/Sign.php index 1d7ceee32..d0021dc98 100644 --- a/src/pocketmine/tile/Sign.php +++ b/src/pocketmine/tile/Sign.php @@ -22,13 +22,14 @@ namespace pocketmine\tile; use pocketmine\level\format\Chunk; +use pocketmine\level\format\FullChunk; use pocketmine\nbt\tag\Compound; use pocketmine\nbt\tag\Int; use pocketmine\nbt\tag\String; class Sign extends Spawnable{ - public function __construct(Chunk $chunk, Compound $nbt){ + public function __construct(FullChunk $chunk, Compound $nbt){ $nbt["id"] = Tile::SIGN; if(!isset($nbt->Text1)){ $nbt->Text1 = new String("Text1", ""); diff --git a/src/pocketmine/tile/Spawnable.php b/src/pocketmine/tile/Spawnable.php index 062bea682..e86387670 100644 --- a/src/pocketmine/tile/Spawnable.php +++ b/src/pocketmine/tile/Spawnable.php @@ -22,6 +22,7 @@ namespace pocketmine\tile; use pocketmine\level\format\Chunk; +use pocketmine\level\format\FullChunk; use pocketmine\nbt\NBT; use pocketmine\nbt\tag\Compound; use pocketmine\network\protocol\EntityDataPacket; @@ -48,7 +49,7 @@ abstract class Spawnable extends Tile{ public abstract function getSpawnCompound(); - public function __construct(Chunk $chunk, Compound $nbt){ + public function __construct(FullChunk $chunk, Compound $nbt){ parent::__construct($chunk, $nbt); $this->spawnToAll(); } diff --git a/src/pocketmine/tile/Tile.php b/src/pocketmine/tile/Tile.php index abbd68107..84d88ff3a 100644 --- a/src/pocketmine/tile/Tile.php +++ b/src/pocketmine/tile/Tile.php @@ -27,6 +27,7 @@ namespace pocketmine\tile; use pocketmine\event\Timings; use pocketmine\level\format\Chunk; +use pocketmine\level\format\FullChunk; use pocketmine\level\format\LevelProvider; use pocketmine\level\Position; use pocketmine\nbt\tag\Compound; @@ -62,7 +63,7 @@ abstract class Tile extends Position{ /** @var \pocketmine\event\TimingsHandler */ public $tickTimer; - public function __construct(Chunk $chunk, Compound $nbt){ + public function __construct(FullChunk $chunk, Compound $nbt){ if($chunk->getLevel() === null){ throw new \Exception("Invalid garbage Chunk given to Tile"); } From 18d13fdc32824a62f480502c2615f1a3c088d111 Mon Sep 17 00:00:00 2001 From: Shoghi Cervantes Date: Thu, 31 Jul 2014 10:19:44 +0200 Subject: [PATCH 11/13] Added threaded chunk sending for all formats --- src/pocketmine/level/Level.php | 41 +----- src/pocketmine/level/format/LevelProvider.php | 10 ++ src/pocketmine/level/format/anvil/Anvil.php | 4 + .../{ => format/anvil}/ChunkRequestTask.php | 9 +- .../format/mcregion/ChunkRequestTask.php | 120 ++++++++++++++++++ .../level/format/mcregion/McRegion.php | 4 + 6 files changed, 147 insertions(+), 41 deletions(-) rename src/pocketmine/level/{ => format/anvil}/ChunkRequestTask.php (94%) create mode 100644 src/pocketmine/level/format/mcregion/ChunkRequestTask.php diff --git a/src/pocketmine/level/Level.php b/src/pocketmine/level/Level.php index 216776db0..0bc901c26 100644 --- a/src/pocketmine/level/Level.php +++ b/src/pocketmine/level/Level.php @@ -1408,45 +1408,12 @@ class Level implements ChunkManager, Metadatable{ } public function requestChunk($x, $z, Player $player, $order = LevelProvider::ORDER_ZXY){ - if($this->blockOrder === $order){ - $player->sendChunk($x, $z, $this->getNetworkChunk($x, $z)); - }else{ - $index = Level::chunkHash($x, $z); - if(!isset($this->chunkSendQueue[$index])){ - $this->chunkSendQueue[$index] = []; - } - - $this->chunkSendQueue[$index][spl_object_hash($player)] = $player; - } - } - - protected function getNetworkChunk($x, $z){ $index = Level::chunkHash($x, $z); - if(ADVANCED_CACHE == true and ($cache = Cache::get("world:".$this->getID().":" . $index)) !== false){ - return $cache; + if(!isset($this->chunkSendQueue[$index])){ + $this->chunkSendQueue[$index] = []; } - $chunk = $this->getChunkAt($x, $z, true); - $tiles = ""; - $nbt = new NBT(NBT::LITTLE_ENDIAN); - foreach($chunk->getTiles() as $tile){ - if($tile instanceof Spawnable){ - $nbt->setData($tile->getSpawnCompound()); - $tiles .= $nbt->write(); - } - } - - $biomeColors = ""; - foreach($chunk->getBiomeColorArray() as $color){ - $biomeColors .= Binary::writeInt($color); - } - - $encoded = 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); - if(ADVANCED_CACHE == true){ - Cache::add("world:".$this->getID().":" . $index, $encoded); - } - - return $encoded; + $this->chunkSendQueue[$index][spl_object_hash($player)] = $player; } protected function processChunkRequest(){ @@ -1467,7 +1434,7 @@ class Level implements ChunkManager, Metadatable{ } unset($this->chunkSendQueue[$index]); }else{ - $task = new ChunkRequestTask($this, $x, $z); + $task = $this->provider->requestChunkTask($x, $z); $this->server->getScheduler()->scheduleAsyncTask($task); $this->chunkSendTasks[$index] = true; } diff --git a/src/pocketmine/level/format/LevelProvider.php b/src/pocketmine/level/format/LevelProvider.php index 3b148b062..6ae2cbf7d 100644 --- a/src/pocketmine/level/format/LevelProvider.php +++ b/src/pocketmine/level/format/LevelProvider.php @@ -52,6 +52,16 @@ interface LevelProvider{ */ public static function usesChunkSection(); + /** + * Requests a MC: PE network chunk to be sent + * + * @param int $x + * @param int $z + * + * @return \pocketmine\scheduler\AsyncTask + */ + public function requestChunkTask($x, $z); + /** @return string */ public function getPath(); diff --git a/src/pocketmine/level/format/anvil/Anvil.php b/src/pocketmine/level/format/anvil/Anvil.php index b21720122..1a87d89db 100644 --- a/src/pocketmine/level/format/anvil/Anvil.php +++ b/src/pocketmine/level/format/anvil/Anvil.php @@ -70,6 +70,10 @@ class Anvil extends McRegion{ return $isValid; } + public function requestChunkTask($x, $z){ + return new ChunkRequestTask($this, $this->getLevel()->getID(), $x, $z); + } + public function loadChunk($chunkX, $chunkZ, $create = false){ $index = Level::chunkHash($chunkX, $chunkZ); if(isset($this->chunks[$index])){ diff --git a/src/pocketmine/level/ChunkRequestTask.php b/src/pocketmine/level/format/anvil/ChunkRequestTask.php similarity index 94% rename from src/pocketmine/level/ChunkRequestTask.php rename to src/pocketmine/level/format/anvil/ChunkRequestTask.php index 01df7af0f..d3d082d5c 100644 --- a/src/pocketmine/level/ChunkRequestTask.php +++ b/src/pocketmine/level/format/anvil/ChunkRequestTask.php @@ -19,8 +19,9 @@ * */ -namespace pocketmine\level; +namespace pocketmine\level\format\anvil; +use pocketmine\level\Level; use pocketmine\nbt\NBT; use pocketmine\scheduler\AsyncTask; use pocketmine\Server; @@ -43,11 +44,11 @@ class ChunkRequestTask extends AsyncTask{ protected $tiles; - public function __construct(Level $level, $chunkX, $chunkZ){ - $this->levelId = $level->getID(); + public function __construct(Anvil $level, $levelId, $chunkX, $chunkZ){ + $this->levelId = $levelId; $this->chunkX = $chunkX; $this->chunkZ = $chunkZ; - $chunk = $level->getChunkAt($chunkX, $chunkZ, true); + $chunk = $level->getChunk($chunkX, $chunkZ, true); $this->biomeIds = $chunk->getBiomeIdArray(); $this->biomeColors = $chunk->getBiomeColorArray(); diff --git a/src/pocketmine/level/format/mcregion/ChunkRequestTask.php b/src/pocketmine/level/format/mcregion/ChunkRequestTask.php new file mode 100644 index 000000000..ad0e7f941 --- /dev/null +++ b/src/pocketmine/level/format/mcregion/ChunkRequestTask.php @@ -0,0 +1,120 @@ +levelId = $levelId; + $this->chunkX = $chunkX; + $this->chunkZ = $chunkZ; + $chunk = $level->getChunk($chunkX, $chunkZ, true); + $this->blocks = $chunk->getBlockIdArray(); + $this->data = $chunk->getBlockDataArray(); + $this->skyLight = $chunk->getBlockSkyLightArray(); + $this->blockLight = $chunk->getBlockLightArray(); + $this->biomeIds = $chunk->getBiomeIdArray(); + $this->biomeColors = $chunk->getBiomeColorArray(); + + $tiles = ""; + $nbt = new NBT(NBT::LITTLE_ENDIAN); + foreach($chunk->getTiles() as $tile){ + if($tile instanceof Spawnable){ + $nbt->setData($tile->getSpawnCompound()); + $tiles .= $nbt->write(); + } + } + + $this->tiles = $tiles; + + $this->compressionLevel = Level::$COMPRESSION_LEVEL; + + } + + public function onRun(){ + $biomeColors = ""; + + foreach($this->biomeColors as $color){ + $biomeColors .= Binary::writeInt($color); + } + + $ordered = zlib_encode(Binary::writeLInt($this->chunkX) . Binary::writeLInt($this->chunkZ) . $this->blocks . $this->data . $this->skyLight . $this->blockLight . $this->biomeIds . $biomeColors . $this->tiles, ZLIB_ENCODING_DEFLATE, $this->compressionLevel); + + $this->setResult($ordered); + } + + public function getColumn(&$data, $x, $z){ + $i = ($z << 4) + $x; + $column = ""; + for($y = 0; $y < 128; ++$y){ + $column .= $data{($y << 8) + $i}; + } + return $column; + } + + public function getHalfColumn(&$data, $x, $z){ + $i = ($z << 3) + ($x >> 1); + $column = ""; + if(($x & 1) === 0){ + for($y = 0; $y < 128; $y += 2){ + $column .= ($data{($y << 7) + $i} & "\x0f") | chr((ord($data{(($y + 1) << 7) + $i}) & 0x0f) << 4); + } + }else{ + for($y = 0; $y < 128; $y += 2){ + $column .= chr((ord($data{($y << 7) + $i}) & 0xf0) >> 4) | ($data{(($y + 1) << 7) + $i} & "\xf0"); + } + } + return $column; + } + + public function onCompletion(Server $server){ + $level = $server->getLevel($this->levelId); + if($level instanceof Level and $this->hasResult()){ + $level->chunkRequestCallback($this->chunkX, $this->chunkZ, $this->getResult()); + } + } + +} \ No newline at end of file diff --git a/src/pocketmine/level/format/mcregion/McRegion.php b/src/pocketmine/level/format/mcregion/McRegion.php index 2a6de7a62..2088a04a6 100644 --- a/src/pocketmine/level/format/mcregion/McRegion.php +++ b/src/pocketmine/level/format/mcregion/McRegion.php @@ -104,6 +104,10 @@ class McRegion extends BaseLevelProvider{ $z = $chunkZ >> 5; } + public function requestChunkTask($x, $z){ + return new ChunkRequestTask($this, $this->getLevel()->getID(), $x, $z); + } + public function unloadChunks(){ $this->chunks = []; } From 95b59793510099361626643aa441a0b3b9dd334c Mon Sep 17 00:00:00 2001 From: Shoghi Cervantes Date: Thu, 31 Jul 2014 14:28:17 +0200 Subject: [PATCH 12/13] Removed SimpleChunk from generator, core, use binary representation of chunks --- src/pocketmine/PocketMine.php | 2 +- src/pocketmine/Server.php | 1 - src/pocketmine/level/Level.php | 41 +-- src/pocketmine/level/format/ChunkSnapshot.php | 93 ------- src/pocketmine/level/format/FullChunk.php | 31 ++- src/pocketmine/level/format/LevelProvider.php | 10 +- src/pocketmine/level/format/SimpleChunk.php | 259 ------------------ src/pocketmine/level/format/anvil/Anvil.php | 40 +-- src/pocketmine/level/format/anvil/Chunk.php | 107 ++++++-- .../level/format/anvil/ChunkRequestTask.php | 5 +- .../level/format/anvil/ChunkSnapshot.php | 78 ------ .../level/format/anvil/RegionLoader.php | 60 +--- .../level/format/generic/BaseChunk.php | 87 +++--- .../format/generic/BaseChunkSnapshot.php | 64 ----- .../level/format/generic/BaseFullChunk.php | 82 +++--- .../level/format/mcregion/Chunk.php | 76 ++++- .../format/mcregion/ChunkRequestTask.php | 5 +- .../level/format/mcregion/ChunkSnapshot.php | 78 ------ .../level/format/mcregion/McRegion.php | 43 +-- .../level/format/mcregion/RegionLoader.php | 56 +--- src/pocketmine/level/generator/Flat.php | 20 +- .../generator/GenerationChunkManager.php | 53 ++-- .../level/generator/GenerationManager.php | 30 +- .../generator/GenerationRequestManager.php | 19 +- .../level/generator/GenerationThread.php | 1 + 25 files changed, 393 insertions(+), 948 deletions(-) delete mode 100644 src/pocketmine/level/format/ChunkSnapshot.php delete mode 100644 src/pocketmine/level/format/SimpleChunk.php delete mode 100644 src/pocketmine/level/format/anvil/ChunkSnapshot.php delete mode 100644 src/pocketmine/level/format/generic/BaseChunkSnapshot.php delete mode 100644 src/pocketmine/level/format/mcregion/ChunkSnapshot.php diff --git a/src/pocketmine/PocketMine.php b/src/pocketmine/PocketMine.php index e277db86c..3294804ac 100644 --- a/src/pocketmine/PocketMine.php +++ b/src/pocketmine/PocketMine.php @@ -141,7 +141,7 @@ namespace pocketmine { } } - gc_disable(); + gc_enable(); error_reporting(-1); ini_set("allow_url_fopen", 1); ini_set("display_errors", 1); diff --git a/src/pocketmine/Server.php b/src/pocketmine/Server.php index 07affa1dd..0cadf4225 100644 --- a/src/pocketmine/Server.php +++ b/src/pocketmine/Server.php @@ -1830,7 +1830,6 @@ class Server{ $lastLoop = 0; $connectionTimer = Timings::$connectionTimer; while($this->isRunning){ - $connectionTimer->startTiming(); foreach($this->interfaces as $interface){ if($interface->process()){ diff --git a/src/pocketmine/level/Level.php b/src/pocketmine/level/Level.php index 0bc901c26..75e90532b 100644 --- a/src/pocketmine/level/Level.php +++ b/src/pocketmine/level/Level.php @@ -40,7 +40,6 @@ use pocketmine\level\format\Chunk; use pocketmine\level\format\FullChunk; use pocketmine\level\format\generic\EmptyChunkSection; use pocketmine\level\format\LevelProvider; -use pocketmine\level\format\SimpleChunk; use pocketmine\level\generator\Generator; use pocketmine\math\AxisAlignedBB; use pocketmine\math\Vector2; @@ -1292,51 +1291,17 @@ class Level implements ChunkManager, Metadatable{ return isset($this->chunks[$index = "$x:$z"]) ? $this->chunks[$index] : $this->chunks[$index] = $this->provider->getChunk($x, $z, $create); } - /** - * @param int $x - * @param int $z - * @param bool $create - * - * @return SimpleChunk - */ - public function getChunk($x, $z, $create = false){ - $chunk = $this->getChunkAt($x, $z, $create); - if($chunk === null){ - return new SimpleChunk($x, $z, 0); - }else{ - $flags = SimpleChunk::FLAG_GENERATED; - if($this->isChunkPopulated($x, $z)){ - $flags |= SimpleChunk::FLAG_POPULATED; - } - $blockIds = []; - $data = []; - - if(!$this->useSections){ - //TODO - return new SimpleChunk($x, $z, 0); - } - - for($Y = 0; $Y < 8; ++$Y){ - $section = $chunk->getSection($Y); - $blockIds[$Y] = $section->getIdArray(); - $data[$Y] = $section->getDataArray(); - } - - return new SimpleChunk($x, $z, $flags, $blockIds, $data); - } - } - - public function generateChunkCallback($x, $z, SimpleChunk $chunk){ + public function generateChunkCallback($x, $z, FullChunk $chunk){ unset($this->chunkGenerationQueue["$x:$z"]); $this->setChunk($x, $z, $chunk); } - public function setChunk($x, $z, SimpleChunk $chunk){ + public function setChunk($x, $z, FullChunk $chunk){ $index = Level::chunkHash($x, $z); foreach($this->getUsingChunk($x, $z) as $player){ $player->unloadChunk($x, $z); } - unset($this->chunks[Level::chunkHash($x, $z)]); + unset($this->chunks[$index]); $this->provider->setChunk($x, $z, $chunk); $this->loadChunk($x, $z); } diff --git a/src/pocketmine/level/format/ChunkSnapshot.php b/src/pocketmine/level/format/ChunkSnapshot.php deleted file mode 100644 index 9a027c672..000000000 --- a/src/pocketmine/level/format/ChunkSnapshot.php +++ /dev/null @@ -1,93 +0,0 @@ -x = $chunkX; - $this->z = $chunkZ; - $this->flags = $flags; - for($y = 0; $y < self::$HEIGHT; ++$y){ - $this->ids[$y] = (isset($ids[$y]) and strlen($ids[$y]) === 4096) ? $ids[$y] : str_repeat("\x00", 4096); - $this->meta[$y] = (isset($meta[$y]) and strlen($meta[$y]) === 2048) ? $meta[$y] : str_repeat("\x00", 2048); - } - } - - public function hasChanged($set = true){ - if($this->changed === true and $set === true){ - $this->changed = false; - return true; - } - - return $this->changed; - } - - /** - * @return int - */ - public function getX(){ - return $this->x; - } - - /** - * @return int - */ - public function getZ(){ - return $this->z; - } - - /** - * @param int $x - */ - public function setX($x){ - $this->x = $x; - } - - /** - * @param int $z - */ - public function setZ($z){ - $this->z = $z; - } - - /** - * @return bool - */ - public function isGenerated(){ - return ($this->flags & self::FLAG_GENERATED) > 0; - } - - /** - * @return bool - */ - public function isPopulated(){ - return ($this->flags & self::FLAG_POPULATED) > 0; - } - - /** - * @param bool $value - */ - public function setGenerated($value = true){ - $this->changed = true; - $this->flags = ($this->flags & ~self::FLAG_GENERATED) | ($value === true ? self::FLAG_GENERATED : 0); - } - - /** - * @param bool $value - */ - public function setPopulated($value = true){ - $this->changed = true; - $this->flags = ($this->flags & ~self::FLAG_POPULATED) | ($value === true ? self::FLAG_POPULATED : 0); - } - - /** - * @param int $x 0-15 - * @param int $y 0-127 - * @param int $z 0-15 - * - * @return int 0-255 - */ - public function getBlockId($x, $y, $z){ - return ord(@$this->ids[$y >> 4]{(($y & 0x0f) << 8) + ($z << 4) + $x}); - } - - /** - * @param int $x 0-15 - * @param int $y 0-127 - * @param int $z 0-15 - * @param int $blockId 0-255 - */ - public function setBlockId($x, $y, $z, $blockId){ - $this->changed = true; - @$this->ids[$y >> 4]{(($y & 0x0f) << 8) + ($z << 4) + $x} = chr($blockId); - } - - /** - * @param int $x 0-15 - * @param int $y 0-127 - * @param int $z 0-15 - * - * @return int 0-15 - */ - public function getBlockData($x, $y, $z){ - $m = ord($this->meta[$y >> 4]{(($y & 0x0f) << 7) + ($z << 3) + ($x >> 1)}); - if(($x & 1) === 0){ - return $m & 0x0F; - }else{ - return $m >> 4; - } - } - - /** - * @param int $x 0-15 - * @param int $y 0-127 - * @param int $z 0-15 - * @param int $data 0-15 - */ - public function setBlockData($x, $y, $z, $data){ - $this->changed = true; - $i = (($y & 0x0f) << 7) + ($z << 3) + ($x >> 1); - $old_m = ord($this->meta[$y >> 4]{$i}); - if(($x & 1) === 0){ - $this->meta[$y >> 4]{$i} = chr(($old_m & 0xf0) | ($data & 0x0f)); - }else{ - $this->meta[$y >> 4]{$i} = chr((($data & 0x0f) << 4) | ($old_m & 0x0f)); - } - } - - /** - * @param int $y 0-7 - * - * @return string - */ - public function getSectionIds($y){ - return $this->ids[$y]; - } - - /** - * @param int $y 0-7 - * - * @return string - */ - public function getSectionData($y){ - return $this->meta[$y]; - } - - /** - * @param int $y 0-7 - * @param string $ids - * @param string $meta - */ - public function setSection($y, $ids = null, $meta = null){ - $this->changed = true; - if($ids !== null){ - $this->ids[$y] = $ids; - } - - if($meta !== null){ - $this->meta[$y] = $meta; - } - } - - /** - * @return string - */ - public function toBinary(){ - $binary = Binary::writeInt($this->x) . Binary::writeInt($this->z) . chr($this->flags); - if($this->isGenerated()){ - for($y = 0; $y < self::$HEIGHT; ++$y){ - $binary .= $this->getSectionIds($y); - $binary .= $this->getSectionData($y); - } - } - - return $binary; - } - - /** - * @param string $binary - * - * @return SimpleChunk - */ - public static function fromBinary($binary){ - $offset = 0; - $chunkX = Binary::readInt(substr($binary, $offset, 4)); - $offset += 4; - $chunkZ = Binary::readInt(substr($binary, $offset, 4)); - $offset += 4; - $flags = ord($binary{$offset++}); - $ids = []; - $meta = []; - if(($flags & self::FLAG_GENERATED) > 0){ - for($y = 0; $y < self::$HEIGHT; ++$y){ - $ids[$y] = substr($binary, $offset, 4096); - $offset += 4096; - $meta[$y] = substr($binary, $offset, 2048); - $offset += 2048; - } - } - - return new SimpleChunk($chunkX, $chunkZ, $flags, $ids, $meta); - } - -} \ No newline at end of file diff --git a/src/pocketmine/level/format/anvil/Anvil.php b/src/pocketmine/level/format/anvil/Anvil.php index 1a87d89db..9781feb9d 100644 --- a/src/pocketmine/level/format/anvil/Anvil.php +++ b/src/pocketmine/level/format/anvil/Anvil.php @@ -21,19 +21,12 @@ namespace pocketmine\level\format\anvil; -use pocketmine\level\format\generic\BaseLevelProvider; +use pocketmine\level\format\FullChunk; use pocketmine\level\format\mcregion\McRegion; -use pocketmine\level\format\SimpleChunk; -use pocketmine\level\generator\Generator; use pocketmine\level\Level; -use pocketmine\nbt\NBT; use pocketmine\nbt\tag\Byte; use pocketmine\nbt\tag\ByteArray; use pocketmine\nbt\tag\Compound; -use pocketmine\nbt\tag\Int; -use pocketmine\nbt\tag\Long; -use pocketmine\nbt\tag\String; -use pocketmine\Player; class Anvil extends McRegion{ @@ -105,8 +98,12 @@ class Anvil extends McRegion{ return parent::getChunk($chunkX, $chunkZ, $create); } - public function setChunk($chunkX, $chunkZ, SimpleChunk $chunk){ - if($chunk->isGenerated() === false){ + public function setChunk($chunkX, $chunkZ, FullChunk $chunk){ + if(!($chunk instanceof Chunk)){ + throw new \Exception("Invalid Chunk class"); + } + + if($chunk->isPopulated() === false){ $this->unloadChunk($chunkX, $chunkZ, false); $regionX = $regionZ = null; self::getRegionIndex($chunkX, $chunkZ, $regionX, $regionZ); @@ -115,26 +112,15 @@ class Anvil extends McRegion{ $region->removeChunk($chunkX - $region->getX() * 32, $chunkZ - $region->getZ() * 32); $this->loadChunk($chunkX, $chunkZ); }else{ - $newChunk = $this->getChunk($chunkX, $chunkZ, true); - for($y = 0; $y < 8; ++$y){ - $section = new ChunkSection(new Compound(null, [ - "Y" => new Byte("Y", $y), - "Blocks" => new ByteArray("Blocks", $chunk->getSectionIds($y)), - "Data" => new ByteArray("Data", $chunk->getSectionData($y)), - "SkyLight" => new ByteArray("SkyLight", str_repeat("\xff", 2048)), //TODO - "BlockLight" => new ByteArray("BlockLight", str_repeat("\x00", 2048)) //TODO - ])); - $newChunk->setSection($y, $section); - } - if($chunk->isPopulated()){ - $newChunk->setPopulated(1); - } + $newChunk = clone $chunk; + $newChunk->setX($chunkX); + $newChunk->setZ($chunkZ); $this->chunks[Level::chunkHash($chunkX, $chunkZ)] = $newChunk; - $this->saveChunk($chunkX, $chunkZ); + //$this->saveChunk($chunkX, $chunkZ); } } - public function createChunkSection($Y){ + public static function createChunkSection($Y){ return new ChunkSection(new Compound(null, [ "Y" => new Byte("Y", $Y), "Blocks" => new ByteArray("Blocks", str_repeat("\xff", 4096)), @@ -146,7 +132,7 @@ class Anvil extends McRegion{ public function isChunkGenerated($chunkX, $chunkZ){ if(($region = $this->getRegion($chunkX >> 5, $chunkZ >> 5)) instanceof RegionLoader){ - return $region->chunkExists($chunkX - $region->getX() * 32, $chunkZ - $region->getZ() * 32); + return $region->chunkExists($chunkX - $region->getX() * 32, $chunkZ - $region->getZ() * 32) and $this->getChunk($chunkX - $region->getX() * 32, $chunkZ - $region->getZ() * 32, true)->isGenerated(); } return false; diff --git a/src/pocketmine/level/format/anvil/Chunk.php b/src/pocketmine/level/format/anvil/Chunk.php index 33a956171..77125e97f 100644 --- a/src/pocketmine/level/format/anvil/Chunk.php +++ b/src/pocketmine/level/format/anvil/Chunk.php @@ -30,6 +30,7 @@ use pocketmine\nbt\tag\ByteArray; use pocketmine\nbt\tag\Compound; use pocketmine\nbt\tag\Enum; use pocketmine\nbt\tag\IntArray; +use pocketmine\Player; use pocketmine\utils\Binary; class Chunk extends BaseChunk{ @@ -37,7 +38,7 @@ class Chunk extends BaseChunk{ /** @var Compound */ protected $nbt; - public function __construct(LevelProvider $level, Compound $nbt){ + public function __construct($level, Compound $nbt){ $this->nbt = $nbt; if(isset($this->nbt->Entities) and $this->nbt->Entities instanceof Enum){ @@ -108,35 +109,18 @@ class Chunk extends BaseChunk{ $this->nbt->TerrainPopulated = new Byte("TerrainPopulated", $value); } - public function getChunkSnapshot($includeMaxBlockY = true, $includeBiome = false, $includeBiomeTemp = false){ - $blockId = ""; - $blockData = ""; - $blockSkyLight = ""; - $blockLight = ""; - $emptySections = [false, false, false, false, false, false, false, false]; + /** + * @return bool + */ + public function isGenerated(){ + return $this->nbt["TerrainPopulated"] > 0 or (isset($this->nbt->TerrainGenerated) and $this->nbt["TerrainGenerated"] > 0); + } - $emptyBlocks = str_repeat("\x00", 4096); - $emptyHalf = str_repeat("\x00", 2048); - - foreach($this->sections as $i => $section){ - if($section instanceof EmptyChunkSection){ - $blockId .= $emptyBlocks; - $blockData .= $emptyHalf; - $blockSkyLight .= $emptyHalf; - $blockLight .= $emptyHalf; - $emptySections[$i] = true; - }else{ - $blockId .= $section->getIdArray(); - $blockData .= $section->getDataArray(); - $blockSkyLight .= $section->getSkyLightArray(); - $blockLight .= $section->getLightArray(); - } - } - - //TODO: maxBlockY, biomeMap, biomeTemp - - //TODO: time - return new ChunkSnapshot($this->getX(), $this->getZ(), $this->getLevel()->getName(), 0 /*$this->getLevel()->getTime()*/, $blockId, $blockData, $blockSkyLight, $blockLight, $emptySections, null, null, null, null); + /** + * @param int $value + */ + public function setGenerated($value = 1){ + $this->nbt->TerrainGenerated = new Byte("TerrainGenerated", $value); } /** @@ -145,4 +129,69 @@ class Chunk extends BaseChunk{ public function getNBT(){ return $this->nbt; } + + /** + * @param string $data + * @param LevelProvider $provider + * + * @return Chunk + */ + public static function fromBinary($data, LevelProvider $provider = null){ + $nbt = new NBT(NBT::BIG_ENDIAN); + $nbt->readCompressed($data, ZLIB_ENCODING_DEFLATE); + $chunk = $nbt->getData(); + + if(!isset($chunk->Level) or !($chunk->Level instanceof Compound)){ + return null; + } + + return new Chunk($provider instanceof LevelProvider ? $provider : "pocketmine\\level\\format\\anvil\\Anvil", $chunk->Level); + } + + public function toBinary(){ + $nbt = $this->getNBT(); + + $nbt->Sections = new Enum("Sections", []); + $nbt->Sections->setTagType(NBT::TAG_Compound); + foreach($this->getSections() as $section){ + $nbt->Sections[$section->getY()] = new Compound(null, [ + "Y" => new Byte("Y", $section->getY()), + "Blocks" => new ByteArray("Blocks", $section->getIdArray()), + "Data" => new ByteArray("Data", $section->getDataArray()), + "BlockLight" => new ByteArray("BlockLight", $section->getLightArray()), + "SkyLight" => new ByteArray("SkyLight", $section->getSkyLightArray()) + ]); + } + + $nbt->Biomes = new ByteArray("Biomes", $this->getBiomeIdArray()); + $nbt->BiomeColors = new IntArray("BiomeColors", $this->getBiomeColorArray()); + + $entities = []; + + foreach($this->getEntities() as $entity){ + if(!($entity instanceof Player) and $entity->closed !== true){ + $entity->saveNBT(); + $entities[] = $entity->namedtag; + } + } + + $nbt->Entities = new Enum("Entities", $entities); + $nbt->Entities->setTagType(NBT::TAG_Compound); + + + $tiles = []; + foreach($this->getTiles() as $tile){ + if($tile->closed !== true){ + $tile->saveNBT(); + $tiles[] = $tile->namedtag; + } + } + + $nbt->Entities = new Enum("TileEntities", $tiles); + $nbt->Entities->setTagType(NBT::TAG_Compound); + $writer = new NBT(NBT::BIG_ENDIAN); + $nbt->setName("Level"); + $writer->setData(new Compound("", array("Level" => $nbt))); + return $writer->writeCompressed(ZLIB_ENCODING_DEFLATE, RegionLoader::$COMPRESSION_LEVEL); + } } \ No newline at end of file diff --git a/src/pocketmine/level/format/anvil/ChunkRequestTask.php b/src/pocketmine/level/format/anvil/ChunkRequestTask.php index d3d082d5c..22661ecce 100644 --- a/src/pocketmine/level/format/anvil/ChunkRequestTask.php +++ b/src/pocketmine/level/format/anvil/ChunkRequestTask.php @@ -48,7 +48,10 @@ class ChunkRequestTask extends AsyncTask{ $this->levelId = $levelId; $this->chunkX = $chunkX; $this->chunkZ = $chunkZ; - $chunk = $level->getChunk($chunkX, $chunkZ, true); + $chunk = $level->getChunk($chunkX, $chunkZ, false); + if(!($chunk instanceof Chunk)){ + throw new \Exception("Invalid Chunk sent"); + } $this->biomeIds = $chunk->getBiomeIdArray(); $this->biomeColors = $chunk->getBiomeColorArray(); diff --git a/src/pocketmine/level/format/anvil/ChunkSnapshot.php b/src/pocketmine/level/format/anvil/ChunkSnapshot.php deleted file mode 100644 index d9e25fc9c..000000000 --- a/src/pocketmine/level/format/anvil/ChunkSnapshot.php +++ /dev/null @@ -1,78 +0,0 @@ -blockId{ - (($y >> 4) << 12) //get section index - + ($y << 8) + ($z << 4) + $x //get block index in section - }); - } - - public function getBlockData($x, $y, $z){ - $data = ord($this->blockData{ - (($y >> 4) << 11) //get section index - + ($y << 7) + ($z << 3) + ($x >> 1) //get block index in section - }); - if(($y & 1) === 0){ - return $data & 0x0F; - }else{ - return $data >> 4; - } - } - - public function getBlockSkyLight($x, $y, $z){ - $level = ord($this->skyLight{ - (($y >> 4) << 11) //get section index - + ($y << 7) + ($z << 3) + ($x >> 1) //get block index in section - }); - if(($y & 1) === 0){ - return $level & 0x0F; - }else{ - return $level >> 4; - } - } - - public function getBlockLight($x, $y, $z){ - $level = ord($this->light{ - (($y >> 4) << 11) //get section index - + ($y << 7) + ($z << 3) + ($x >> 1) //get block index in section - }); - if(($y & 1) === 0){ - return $level & 0x0F; - }else{ - return $level >> 4; - } - } - - public function getBiome(){ - return 0; //TODO - } - - public function getHighestBlockAt($x, $z){ - return 127; //TODO - } -} \ No newline at end of file diff --git a/src/pocketmine/level/format/anvil/RegionLoader.php b/src/pocketmine/level/format/anvil/RegionLoader.php index b465e1be0..c8a2885c2 100644 --- a/src/pocketmine/level/format/anvil/RegionLoader.php +++ b/src/pocketmine/level/format/anvil/RegionLoader.php @@ -22,6 +22,7 @@ namespace pocketmine\level\format\anvil; use pocketmine\level\format\LevelProvider; +use pocketmine\level\format\mcregion\Chunk; use pocketmine\nbt\NBT; use pocketmine\nbt\tag\Byte; use pocketmine\nbt\tag\ByteArray; @@ -97,16 +98,9 @@ class RegionLoader extends \pocketmine\level\format\mcregion\RegionLoader{ return false; } - $nbt = new NBT(NBT::BIG_ENDIAN); - $nbt->readCompressed(fread($this->filePointer, $length - 1), $compression); - $chunk = $nbt->getData(); + $chunk = Chunk::fromBinary(fread($this->filePointer, $length - 1), $this->levelProvider); - - if(!isset($chunk->Level) or !($chunk->Level instanceof Compound)){ - return false; - } - - return new Chunk($this->levelProvider, $chunk->Level); + return $chunk instanceof Chunk ? $chunk : false; } public function generateChunk($x, $z){ @@ -129,51 +123,15 @@ class RegionLoader extends \pocketmine\level\format\mcregion\RegionLoader{ $nbt->TileEntities->setTagType(NBT::TAG_Compound); $nbt->TileTicks = new Enum("TileTicks", []); $nbt->TileTicks->setTagType(NBT::TAG_Compound); - $this->saveChunk($x, $z, $nbt); + $writer = new NBT(NBT::BIG_ENDIAN); + $nbt->setName("Level"); + $writer->setData(new Compound("", array("Level" => $nbt))); + $chunkData= $writer->writeCompressed(ZLIB_ENCODING_DEFLATE, RegionLoader::$COMPRESSION_LEVEL); + $this->saveChunk($x, $z, $chunkData); } public function writeChunk(Chunk $chunk){ - $nbt = $chunk->getNBT(); - $nbt->Sections = new Enum("Sections", []); - $nbt->Sections->setTagType(NBT::TAG_Compound); - foreach($chunk->getSections() as $section){ - $nbt->Sections[$section->getY()] = new Compound(null, [ - "Y" => new Byte("Y", $section->getY()), - "Blocks" => new ByteArray("Blocks", $section->getIdArray()), - "Data" => new ByteArray("Data", $section->getDataArray()), - "BlockLight" => new ByteArray("BlockLight", $section->getLightArray()), - "SkyLight" => new ByteArray("SkyLight", $section->getSkyLightArray()) - ]); - } - - $nbt->Biomes = new ByteArray("Biomes", $chunk->getBiomeIdArray()); - $nbt->BiomeColors = new IntArray("BiomeColors", $chunk->getBiomeColorArray()); - - $entities = []; - - foreach($chunk->getEntities() as $entity){ - if(!($entity instanceof Player) and $entity->closed !== true){ - $entity->saveNBT(); - $entities[] = $entity->namedtag; - } - } - - $nbt->Entities = new Enum("Entities", $entities); - $nbt->Entities->setTagType(NBT::TAG_Compound); - - - $tiles = []; - foreach($chunk->getTiles() as $tile){ - if($tile->closed !== true){ - $tile->saveNBT(); - $tiles[] = $tile->namedtag; - } - } - - $nbt->Entities = new Enum("TileEntities", $tiles); - $nbt->Entities->setTagType(NBT::TAG_Compound); - - $this->saveChunk($chunk->getX() - ($this->getX() * 32), $chunk->getZ() - ($this->getZ() * 32), $nbt); + $this->saveChunk($chunk->getX() - ($this->getX() * 32), $chunk->getZ() - ($this->getZ() * 32), $chunk->toBinary()); } } \ No newline at end of file diff --git a/src/pocketmine/level/format/generic/BaseChunk.php b/src/pocketmine/level/format/generic/BaseChunk.php index 5348f40ed..50e43c87e 100644 --- a/src/pocketmine/level/format/generic/BaseChunk.php +++ b/src/pocketmine/level/format/generic/BaseChunk.php @@ -70,8 +70,8 @@ abstract class BaseChunk extends BaseFullChunk implements Chunk{ * * @throws \Exception */ - protected function __construct(LevelProvider $level, $x, $z, array $sections, $biomeIds = null, array $biomeColors = [], array $entities = [], array $tiles = []){ - $this->level = new \WeakRef($level); + protected function __construct($level, $x, $z, array $sections, $biomeIds = null, array $biomeColors = [], array $entities = [], array $tiles = []){ + $this->level = $level instanceof LevelProvider ? new \WeakRef($level) : $level; $this->x = (int) $x; $this->z = (int) $z; foreach($sections as $Y => $section){ @@ -99,46 +99,48 @@ abstract class BaseChunk extends BaseFullChunk implements Chunk{ $this->biomeColors = array_fill(0, 256, Binary::readInt("\x00\x85\xb2\x4a")); } - $this->getLevel()->getLevel()->timings->syncChunkLoadEntitiesTimer->startTiming(); - foreach($entities as $nbt){ - if($nbt instanceof Compound){ - if(!isset($nbt->id)){ - continue; - } + if($this->getLevel() instanceof LevelProvider){ + $this->getLevel()->getLevel()->timings->syncChunkLoadEntitiesTimer->startTiming(); + foreach($entities as $nbt){ + if($nbt instanceof Compound){ + if(!isset($nbt->id)){ + continue; + } - if($nbt->id instanceof String){ //New format + if($nbt->id instanceof String){ //New format + switch($nbt["id"]){ + case "Item": + (new DroppedItem($this, $nbt))->spawnToAll(); + break; + } + }else{ //Old format + + } + } + } + $this->getLevel()->getLevel()->timings->syncChunkLoadEntitiesTimer->stopTiming(); + + $this->getLevel()->getLevel()->timings->syncChunkLoadTileEntitiesTimer->startTiming(); + foreach($tiles as $nbt){ + if($nbt instanceof Compound){ + if(!isset($nbt->id)){ + continue; + } switch($nbt["id"]){ - case "Item": - (new DroppedItem($this, $nbt))->spawnToAll(); + case Tile::CHEST: + new Chest($this, $nbt); + break; + case Tile::FURNACE: + new Furnace($this, $nbt); + break; + case Tile::SIGN: + new Sign($this, $nbt); break; } - }else{ //Old format - } } + $this->getLevel()->getLevel()->timings->syncChunkLoadTileEntitiesTimer->stopTiming(); } - $this->getLevel()->getLevel()->timings->syncChunkLoadEntitiesTimer->stopTiming(); - - $this->getLevel()->getLevel()->timings->syncChunkLoadTileEntitiesTimer->startTiming(); - foreach($tiles as $nbt){ - if($nbt instanceof Compound){ - if(!isset($nbt->id)){ - continue; - } - switch($nbt["id"]){ - case Tile::CHEST: - new Chest($this, $nbt); - break; - case Tile::FURNACE: - new Furnace($this, $nbt); - break; - case Tile::SIGN: - new Sign($this, $nbt); - break; - } - } - } - $this->getLevel()->getLevel()->timings->syncChunkLoadTileEntitiesTimer->stopTiming(); } public function getBlock($x, $y, $z, &$blockId, &$meta = null){ @@ -149,7 +151,8 @@ abstract class BaseChunk extends BaseFullChunk implements Chunk{ try{ return $this->sections[$y >> 4]->setBlock($x, $y & 0x0f, $z, $blockId & 0xff, $meta & 0x0f); }catch(\Exception $e){ - $this->setSection($Y = $y >> 4, $this->getLevel()->createChunkSection($Y)); + $level = $this->getLevel(); + $this->setSection($Y = $y >> 4, $level::createChunkSection($Y)); return $this->setBlock($x, $y, $z, $blockId, $meta); } } @@ -162,7 +165,8 @@ abstract class BaseChunk extends BaseFullChunk implements Chunk{ try{ $this->sections[$y >> 4]->setBlockId($x, $y & 0x0f, $z, $id); }catch(\Exception $e){ - $this->setSection($Y = $y >> 4, $this->getLevel()->createChunkSection($Y)); + $level = $this->getLevel(); + $this->setSection($Y = $y >> 4, $level::createChunkSection($Y)); $this->setBlockId($x, $y, $z, $id); } } @@ -175,7 +179,8 @@ abstract class BaseChunk extends BaseFullChunk implements Chunk{ try{ $this->sections[$y >> 4]->setBlockData($x, $y & 0x0f, $z, $data); }catch(\Exception $e){ - $this->setSection($Y = $y >> 4, $this->getLevel()->createChunkSection($Y)); + $level = $this->getLevel(); + $this->setSection($Y = $y >> 4, $level::createChunkSection($Y)); $this->setBlockData($x, $y, $z, $data); } } @@ -188,7 +193,8 @@ abstract class BaseChunk extends BaseFullChunk implements Chunk{ try{ $this->sections[$y >> 4]->getBlockSkyLight($x, $y & 0x0f, $z, $data); }catch(\Exception $e){ - $this->setSection($Y = $y >> 4, $this->getLevel()->createChunkSection($Y)); + $level = $this->getLevel(); + $this->setSection($Y = $y >> 4, $level::createChunkSection($Y)); $this->setBlockSkyLight($x, $y, $z, $data); } } @@ -201,7 +207,8 @@ abstract class BaseChunk extends BaseFullChunk implements Chunk{ try{ $this->sections[$y >> 4]->getBlockSkyLight($x, $y & 0x0f, $z, $data); }catch(\Exception $e){ - $this->setSection($Y = $y >> 4, $this->getLevel()->createChunkSection($Y)); + $level = $this->getLevel(); + $this->setSection($Y = $y >> 4, $level::createChunkSection($Y)); $this->setBlockLight($x, $y, $z, $data); } } diff --git a/src/pocketmine/level/format/generic/BaseChunkSnapshot.php b/src/pocketmine/level/format/generic/BaseChunkSnapshot.php deleted file mode 100644 index 4d08b4f2d..000000000 --- a/src/pocketmine/level/format/generic/BaseChunkSnapshot.php +++ /dev/null @@ -1,64 +0,0 @@ -x = $x; - $this->z = $z; - $this->levelName = $levelName; - $this->levelTime = $levelTime; - $this->blockId = $blockId; - $this->blockData = $blockData; - $this->skyLight = $skyLight; - $this->light = $light; - } - - public function getX(){ - return $this->x; - } - - public function getZ(){ - return $this->z; - } - - public function getLevelName(){ - return $this->levelName; - } - - public function getLevelTime(){ - return $this->levelTime; - } -} \ No newline at end of file diff --git a/src/pocketmine/level/format/generic/BaseFullChunk.php b/src/pocketmine/level/format/generic/BaseFullChunk.php index 6d4ae583b..232abae55 100644 --- a/src/pocketmine/level/format/generic/BaseFullChunk.php +++ b/src/pocketmine/level/format/generic/BaseFullChunk.php @@ -76,8 +76,8 @@ abstract class BaseFullChunk implements FullChunk{ * * @throws \Exception */ - protected function __construct(LevelProvider $level, $x, $z, $blocks, $data, $skyLight, $blockLight, $biomeIds = null, array $biomeColors = [], array $entities = [], array $tiles = []){ - $this->level = new \WeakRef($level); + protected function __construct($level, $x, $z, $blocks, $data, $skyLight, $blockLight, $biomeIds = null, array $biomeColors = [], array $entities = [], array $tiles = []){ + $this->level = $level instanceof LevelProvider ? new \WeakRef($level) : $level; $this->x = (int) $x; $this->z = (int) $z; @@ -98,46 +98,48 @@ abstract class BaseFullChunk implements FullChunk{ $this->biomeColors = array_fill(0, 256, Binary::readInt("\x00\x85\xb2\x4a")); } - $this->getLevel()->getLevel()->timings->syncChunkLoadEntitiesTimer->startTiming(); - foreach($entities as $nbt){ - if($nbt instanceof Compound){ - if(!isset($nbt->id)){ - continue; - } + if($this->getLevel() instanceof LevelProvider){ + $this->getLevel()->getLevel()->timings->syncChunkLoadEntitiesTimer->startTiming(); + foreach($entities as $nbt){ + if($nbt instanceof Compound){ + if(!isset($nbt->id)){ + continue; + } - if($nbt->id instanceof String){ //New format + if($nbt->id instanceof String){ //New format + switch($nbt["id"]){ + case "Item": + (new DroppedItem($this, $nbt))->spawnToAll(); + break; + } + }else{ //Old format + + } + } + } + $this->getLevel()->getLevel()->timings->syncChunkLoadEntitiesTimer->stopTiming(); + + $this->getLevel()->getLevel()->timings->syncChunkLoadTileEntitiesTimer->startTiming(); + foreach($tiles as $nbt){ + if($nbt instanceof Compound){ + if(!isset($nbt->id)){ + continue; + } switch($nbt["id"]){ - case "Item": - (new DroppedItem($this, $nbt))->spawnToAll(); + case Tile::CHEST: + new Chest($this, $nbt); + break; + case Tile::FURNACE: + new Furnace($this, $nbt); + break; + case Tile::SIGN: + new Sign($this, $nbt); break; } - }else{ //Old format - } } + $this->getLevel()->getLevel()->timings->syncChunkLoadTileEntitiesTimer->stopTiming(); } - $this->getLevel()->getLevel()->timings->syncChunkLoadEntitiesTimer->stopTiming(); - - $this->getLevel()->getLevel()->timings->syncChunkLoadTileEntitiesTimer->startTiming(); - foreach($tiles as $nbt){ - if($nbt instanceof Compound){ - if(!isset($nbt->id)){ - continue; - } - switch($nbt["id"]){ - case Tile::CHEST: - new Chest($this, $nbt); - break; - case Tile::FURNACE: - new Furnace($this, $nbt); - break; - case Tile::SIGN: - new Sign($this, $nbt); - break; - } - } - } - $this->getLevel()->getLevel()->timings->syncChunkLoadTileEntitiesTimer->stopTiming(); } public function getX(){ @@ -148,11 +150,19 @@ abstract class BaseFullChunk implements FullChunk{ return $this->z; } + public function setX($x){ + $this->x = $x; + } + + public function setZ($z){ + $this->z = $z; + } + /** * @return LevelProvider */ public function getLevel(){ - return $this->level->valid() ? $this->level->get() : null; + return $this->level instanceof \WeakRef ? ($this->level->valid() ? $this->level->get() : null) : $this->level; } public function getBiomeId($x, $z){ diff --git a/src/pocketmine/level/format/mcregion/Chunk.php b/src/pocketmine/level/format/mcregion/Chunk.php index 52fe2ddff..d5ba9054d 100644 --- a/src/pocketmine/level/format/mcregion/Chunk.php +++ b/src/pocketmine/level/format/mcregion/Chunk.php @@ -29,6 +29,7 @@ use pocketmine\nbt\tag\ByteArray; use pocketmine\nbt\tag\Compound; use pocketmine\nbt\tag\Enum; use pocketmine\nbt\tag\IntArray; +use pocketmine\Player; use pocketmine\utils\Binary; class Chunk extends BaseFullChunk{ @@ -36,7 +37,7 @@ class Chunk extends BaseFullChunk{ /** @var Compound */ protected $nbt; - public function __construct(LevelProvider $level, Compound $nbt){ + public function __construct($level, Compound $nbt){ $this->nbt = $nbt; if(isset($this->nbt->Entities) and $this->nbt->Entities instanceof Enum){ @@ -209,13 +210,76 @@ class Chunk extends BaseFullChunk{ $this->nbt->TerrainPopulated = new Byte("TerrainPopulated", $value); } - public function getChunkSnapshot($includeMaxBlockY = true, $includeBiome = false, $includeBiomeTemp = false){ - $emptySections = [false, false, false, false, false, false, false, false]; + /** + * @return bool + */ + public function isGenerated(){ + return $this->nbt["TerrainPopulated"] > 0 or (isset($this->nbt->TerrainGenerated) and $this->nbt["TerrainGenerated"] > 0); + } - //TODO: maxBlockY, biomeMap, biomeTemp + /** + * @param int $value + */ + public function setGenerated($value = 1){ + $this->nbt->TerrainGenerated = new Byte("TerrainGenerated", $value); + } - //TODO: time - return new ChunkSnapshot($this->getX(), $this->getZ(), $this->getLevel()->getName(), 0 /*$this->getLevel()->getTime()*/, $this->getBlockIdArray(), $this->getBlockDataArray(), $this->getBlockSkyLightArray(), $this->getBlockLightArray(), $emptySections, null, null, null, null); + /** + * @param string $data + * @param LevelProvider $provider + * + * @return Chunk + */ + public static function fromBinary($data, LevelProvider $provider = null){ + $nbt = new NBT(NBT::BIG_ENDIAN); + $nbt->readCompressed($data, ZLIB_ENCODING_DEFLATE); + $chunk = $nbt->getData(); + + if(!isset($chunk->Level) or !($chunk->Level instanceof Compound)){ + return null; + } + + return new Chunk($provider instanceof LevelProvider ? $provider : "pocketmine\\level\\format\\mcregion\\McRegion", $chunk->Level); + } + + public function toBinary(){ + $nbt = $this->getNBT(); + + $nbt->Blocks = new ByteArray("Blocks", $this->getBlockIdArray()); + $nbt->Data = new ByteArray("Data", $this->getBlockDataArray()); + $nbt->SkyLight = new ByteArray("SkyLight", $this->getBlockSkyLightArray()); + $nbt->BlockLight = new ByteArray("BlockLight", $this->getBlockLightArray()); + + $nbt->Biomes = new ByteArray("Biomes", $this->getBiomeIdArray()); + $nbt->BiomeColors = new IntArray("BiomeColors", $this->getBiomeColorArray()); + + $entities = []; + + foreach($this->getEntities() as $entity){ + if(!($entity instanceof Player) and $entity->closed !== true){ + $entity->saveNBT(); + $entities[] = $entity->namedtag; + } + } + + $nbt->Entities = new Enum("Entities", $entities); + $nbt->Entities->setTagType(NBT::TAG_Compound); + + + $tiles = []; + foreach($this->getTiles() as $tile){ + if($tile->closed !== true){ + $tile->saveNBT(); + $tiles[] = $tile->namedtag; + } + } + + $nbt->Entities = new Enum("TileEntities", $tiles); + $nbt->Entities->setTagType(NBT::TAG_Compound); + $writer = new NBT(NBT::BIG_ENDIAN); + $nbt->setName("Level"); + $writer->setData(new Compound("", array("Level" => $nbt))); + return $writer->writeCompressed(ZLIB_ENCODING_DEFLATE, RegionLoader::$COMPRESSION_LEVEL); } /** diff --git a/src/pocketmine/level/format/mcregion/ChunkRequestTask.php b/src/pocketmine/level/format/mcregion/ChunkRequestTask.php index ad0e7f941..badd3c419 100644 --- a/src/pocketmine/level/format/mcregion/ChunkRequestTask.php +++ b/src/pocketmine/level/format/mcregion/ChunkRequestTask.php @@ -51,7 +51,10 @@ class ChunkRequestTask extends AsyncTask{ $this->levelId = $levelId; $this->chunkX = $chunkX; $this->chunkZ = $chunkZ; - $chunk = $level->getChunk($chunkX, $chunkZ, true); + $chunk = $level->getChunk($chunkX, $chunkZ, false); + if(!($chunk instanceof Chunk)){ + throw new \Exception("Invalid Chunk sent"); + } $this->blocks = $chunk->getBlockIdArray(); $this->data = $chunk->getBlockDataArray(); $this->skyLight = $chunk->getBlockSkyLightArray(); diff --git a/src/pocketmine/level/format/mcregion/ChunkSnapshot.php b/src/pocketmine/level/format/mcregion/ChunkSnapshot.php deleted file mode 100644 index ce0e9f91e..000000000 --- a/src/pocketmine/level/format/mcregion/ChunkSnapshot.php +++ /dev/null @@ -1,78 +0,0 @@ -blockId{ - (($y >> 4) << 12) //get section index - + ($y << 8) + ($z << 4) + $x //get block index in section - }); - } - - public function getBlockData($x, $y, $z){ - $data = ord($this->blockData{ - (($y >> 4) << 11) //get section index - + ($y << 7) + ($z << 3) + ($x >> 1) //get block index in section - }); - if(($y & 1) === 0){ - return $data & 0x0F; - }else{ - return $data >> 4; - } - } - - public function getBlockSkyLight($x, $y, $z){ - $level = ord($this->skyLight{ - (($y >> 4) << 11) //get section index - + ($y << 7) + ($z << 3) + ($x >> 1) //get block index in section - }); - if(($y & 1) === 0){ - return $level & 0x0F; - }else{ - return $level >> 4; - } - } - - public function getBlockLight($x, $y, $z){ - $level = ord($this->light{ - (($y >> 4) << 11) //get section index - + ($y << 7) + ($z << 3) + ($x >> 1) //get block index in section - }); - if(($y & 1) === 0){ - return $level & 0x0F; - }else{ - return $level >> 4; - } - } - - public function getBiome(){ - return 0; //TODO - } - - public function getHighestBlockAt($x, $z){ - return 127; //TODO - } -} \ No newline at end of file diff --git a/src/pocketmine/level/format/mcregion/McRegion.php b/src/pocketmine/level/format/mcregion/McRegion.php index 2088a04a6..1c4b0c13d 100644 --- a/src/pocketmine/level/format/mcregion/McRegion.php +++ b/src/pocketmine/level/format/mcregion/McRegion.php @@ -21,8 +21,8 @@ namespace pocketmine\level\format\mcregion; +use pocketmine\level\format\FullChunk; use pocketmine\level\format\generic\BaseLevelProvider; -use pocketmine\level\format\SimpleChunk; use pocketmine\level\generator\Generator; use pocketmine\level\Level; use pocketmine\nbt\NBT; @@ -219,12 +219,12 @@ class McRegion extends BaseLevelProvider{ } } - public function setChunk($chunkX, $chunkZ, SimpleChunk $chunk){ - return; - //TODO! + public function setChunk($chunkX, $chunkZ, FullChunk $chunk){ + if(!($chunk instanceof Chunk)){ + throw new \Exception("Invalid Chunk class"); + } - - if($chunk->isGenerated() === false){ + if($chunk->isPopulated() === false){ $this->unloadChunk($chunkX, $chunkZ, false); $regionX = $regionZ = null; self::getRegionIndex($chunkX, $chunkZ, $regionX, $regionZ); @@ -233,38 +233,21 @@ class McRegion extends BaseLevelProvider{ $region->removeChunk($chunkX - $region->getX() * 32, $chunkZ - $region->getZ() * 32); $this->loadChunk($chunkX, $chunkZ); }else{ - $newChunk = $this->getChunk($chunkX, $chunkZ, true); - for($y = 0; $y < 8; ++$y){ - /*$section = new ChunkSection(new Compound(null, [ - "Y" => new Byte("Y", $y), - "Blocks" => new ByteArray("Blocks", $chunk->getSectionIds($y)), - "Data" => new ByteArray("Data", $chunk->getSectionData($y)), - "SkyLight" => new ByteArray("SkyLight", str_repeat("\xff", 2048)), //TODO - "BlockLight" => new ByteArray("BlockLight", str_repeat("\x00", 2048)) //TODO - ])); - $newChunk->setSection($y, $section);*/ - } - if($chunk->isPopulated()){ - $newChunk->setPopulated(1); - } + $newChunk = clone $chunk; + $newChunk->setX($chunkX); + $newChunk->setZ($chunkZ); $this->chunks[Level::chunkHash($chunkX, $chunkZ)] = $newChunk; - $this->saveChunk($chunkX, $chunkZ); + //$this->saveChunk($chunkX, $chunkZ); } } - public function createChunkSection($Y){ - return new ChunkSection( - $Y, - str_repeat("\xff", 4096), - $half = str_repeat("\xff", 2048), - $half, - $half - ); + public static function createChunkSection($Y){ + return null; } public function isChunkGenerated($chunkX, $chunkZ){ if(($region = $this->getRegion($chunkX >> 5, $chunkZ >> 5)) instanceof RegionLoader){ - return $region->chunkExists($chunkX - $region->getX() * 32, $chunkZ - $region->getZ() * 32); + return $region->chunkExists($chunkX - $region->getX() * 32, $chunkZ - $region->getZ() * 32) and $this->getChunk($chunkX - $region->getX() * 32, $chunkZ - $region->getZ() * 32, true)->isGenerated(); } return false; diff --git a/src/pocketmine/level/format/mcregion/RegionLoader.php b/src/pocketmine/level/format/mcregion/RegionLoader.php index 4021574ba..a15299abb 100644 --- a/src/pocketmine/level/format/mcregion/RegionLoader.php +++ b/src/pocketmine/level/format/mcregion/RegionLoader.php @@ -119,16 +119,9 @@ class RegionLoader{ return false; } - $nbt = new NBT(NBT::BIG_ENDIAN); - $nbt->readCompressed(fread($this->filePointer, $length - 1), $compression); - $chunk = $nbt->getData(); + $chunk = Chunk::fromBinary(fread($this->filePointer, $length - 1), $this->levelProvider); - - if(!isset($chunk->Level) or !($chunk->Level instanceof Compound)){ - return false; - } - - return new Chunk($this->levelProvider, $chunk->Level); + return $chunk instanceof Chunk ? $chunk : false; } public function chunkExists($x, $z){ @@ -159,14 +152,15 @@ class RegionLoader{ $nbt->TileEntities->setTagType(NBT::TAG_Compound); $nbt->TileTicks = new Enum("TileTicks", []); $nbt->TileTicks->setTagType(NBT::TAG_Compound); - $this->saveChunk($x, $z, $nbt); - } - - protected function saveChunk($x, $z, Compound $nbt){ $writer = new NBT(NBT::BIG_ENDIAN); $nbt->setName("Level"); $writer->setData(new Compound("", array("Level" => $nbt))); $chunkData = $writer->writeCompressed(ZLIB_ENCODING_DEFLATE, self::$COMPRESSION_LEVEL); + + $this->saveChunk($x, $z, $chunkData); + } + + protected function saveChunk($x, $z, $chunkData){ $length = strlen($chunkData) + 1; $sectors = (int) ceil(($length + 4) / 4096); $index = self::getChunkOffset($x, $z); @@ -187,41 +181,7 @@ class RegionLoader{ } public function writeChunk(Chunk $chunk){ - $nbt = $chunk->getNBT(); - - $nbt->Blocks = new ByteArray("Blocks", $chunk->getBlockIdArray()); - $nbt->Data = new ByteArray("Data", $chunk->getBlockDataArray()); - $nbt->SkyLight = new ByteArray("SkyLight", $chunk->getBlockSkyLightArray()); - $nbt->BlockLight = new ByteArray("BlockLight", $chunk->getBlockLightArray()); - - $nbt->Biomes = new ByteArray("Biomes", $chunk->getBiomeIdArray()); - $nbt->BiomeColors = new IntArray("BiomeColors", $chunk->getBiomeColorArray()); - - $entities = []; - - foreach($chunk->getEntities() as $entity){ - if(!($entity instanceof Player) and $entity->closed !== true){ - $entity->saveNBT(); - $entities[] = $entity->namedtag; - } - } - - $nbt->Entities = new Enum("Entities", $entities); - $nbt->Entities->setTagType(NBT::TAG_Compound); - - - $tiles = []; - foreach($chunk->getTiles() as $tile){ - if($tile->closed !== true){ - $tile->saveNBT(); - $tiles[] = $tile->namedtag; - } - } - - $nbt->Entities = new Enum("TileEntities", $tiles); - $nbt->Entities->setTagType(NBT::TAG_Compound); - - $this->saveChunk($chunk->getX() - ($this->getX() * 32), $chunk->getZ() - ($this->getZ() * 32), $nbt); + $this->saveChunk($chunk->getX() - ($this->getX() * 32), $chunk->getZ() - ($this->getZ() * 32), $chunk->toBinary()); } protected static function getChunkOffset($x, $z){ diff --git a/src/pocketmine/level/generator/Flat.php b/src/pocketmine/level/generator/Flat.php index 5cab14e55..9c03a9dda 100644 --- a/src/pocketmine/level/generator/Flat.php +++ b/src/pocketmine/level/generator/Flat.php @@ -21,6 +21,7 @@ namespace pocketmine\level\generator; +use pocketmine\level\format\FullChunk; use pocketmine\block\CoalOre; use pocketmine\block\DiamondOre; use pocketmine\block\Dirt; @@ -30,7 +31,6 @@ use pocketmine\block\IronOre; use pocketmine\block\LapisOre; use pocketmine\block\RedstoneOre; use pocketmine\item\Item; -use pocketmine\level\format\SimpleChunk; use pocketmine\level\generator\populator\Ore; use pocketmine\level\generator\populator\Populator; use pocketmine\math\Vector3 as Vector3; @@ -39,7 +39,7 @@ use pocketmine\utils\Random; class Flat extends Generator{ /** @var GenerationChunkManager */ private $level; - /** @var SimpleChunk */ + /** @var FullChunk */ private $chunk; /** @var Random */ private $random; @@ -59,11 +59,7 @@ class Flat extends Generator{ $this->preset = "2;7,2x3,2;1;"; //$this->preset = "2;7,59x1,3x3,2;1;spawn(radius=10 block=89),decoration(treecount=80 grasscount=45)"; $this->options = $options; - if(isset($options["preset"]) and $options["preset"] != ""){ - $this->parsePreset($options["preset"]); - }else{ - $this->parsePreset($this->preset); - } + if(isset($this->options["decoration"])){ $ores = new Ore(); $ores->setOreTypes(array( @@ -110,7 +106,8 @@ class Flat extends Generator{ } - $this->chunk = new SimpleChunk(null, null, SimpleChunk::FLAG_GENERATED); + $this->chunk = $this->level->getChunk(0, 0); + $this->chunk->setGenerated(); for($Z = 0; $Z < 16; ++$Z){ for($X = 0; $X < 16; ++$X){ @@ -145,6 +142,13 @@ class Flat extends Generator{ public function init(GenerationChunkManager $level, Random $random){ $this->level = $level; $this->random = $random; + + if(isset($this->options["preset"]) and $this->options["preset"] != ""){ + $this->parsePreset($this->options["preset"]); + }else{ + $this->parsePreset($this->preset); + } + } public function generateChunk($chunkX, $chunkZ){ diff --git a/src/pocketmine/level/generator/GenerationChunkManager.php b/src/pocketmine/level/generator/GenerationChunkManager.php index 757afc8df..eba2e89cf 100644 --- a/src/pocketmine/level/generator/GenerationChunkManager.php +++ b/src/pocketmine/level/generator/GenerationChunkManager.php @@ -22,7 +22,7 @@ namespace pocketmine\level\generator; use pocketmine\level\ChunkManager; -use pocketmine\level\format\SimpleChunk; +use pocketmine\level\format\FullChunk; use pocketmine\level\Level; use pocketmine\utils\Random; @@ -30,10 +30,10 @@ class GenerationChunkManager implements ChunkManager{ protected $levelID; - /** @var SimpleChunk[] */ + /** @var FullChunk[] */ protected $chunks = []; - /** @var \SplObjectStorage */ + /** @var \SplObjectStorage */ protected $unloadQueue; /** @var Generator */ @@ -44,6 +44,8 @@ class GenerationChunkManager implements ChunkManager{ protected $seed; + protected $changes = []; + public function __construct(GenerationManager $manager, $levelID, $seed, $class, array $options){ if(!is_subclass_of($class, "pocketmine\\level\\generator\\Generator")){ throw new \Exception("Class is not a subclass of Generator"); @@ -77,7 +79,7 @@ class GenerationChunkManager implements ChunkManager{ * @param $chunkX * @param $chunkZ * - * @return SimpleChunk + * @return FullChunk */ public function getChunk($chunkX, $chunkZ){ $index = Level::chunkHash($chunkX, $chunkZ); @@ -87,28 +89,24 @@ class GenerationChunkManager implements ChunkManager{ } /** - * @param bool $set - * - * @return SimpleChunk[] + * @return FullChunk[] */ - public function getChangedChunks($set = true){ - $changed = []; - foreach($this->chunks as $chunk){ - if($chunk->hasChanged($set)){ - $changed[] = $chunk; - } - } + public function getChangedChunks(){ + return $this->changes; + } - return $changed; + public function cleanChangedChunks(){ + $this->changes = []; } public function doGarbageCollection(){ if($this->unloadQueue->count() > 0){ - /** @var SimpleChunk $chunk */ + /** @var FullChunk $chunk */ foreach($this->unloadQueue as $chunk){ - if(!$chunk->hasChanged(false)){ - unset($this->chunks[Level::chunkHash($chunk->getX(), $chunk->getZ())]); + if(isset($this->changes[$index = Level::chunkHash($chunk->getX(), $chunk->getZ())])){ + continue; } + unset($this->chunks[$index]); $this->unloadQueue->detach($chunk); } } @@ -119,7 +117,7 @@ class GenerationChunkManager implements ChunkManager{ } public function generateChunk($chunkX, $chunkZ){ - $this->chunks[Level::chunkHash($chunkX, $chunkZ)] = new SimpleChunk($chunkX, $chunkZ, 0); + $this->chunks[Level::chunkHash($chunkX, $chunkZ)] = $this->requestChunk($chunkX, $chunkZ); $this->generator->generateChunk($chunkX, $chunkZ); $this->setChunkGenerated($chunkX, $chunkZ); } @@ -150,11 +148,15 @@ class GenerationChunkManager implements ChunkManager{ } public function setChunkGenerated($chunkX, $chunkZ){ - $this->getChunk($chunkX, $chunkZ)->setGenerated(true); + $chunk = $this->getChunk($chunkX, $chunkZ); + $chunk->setGenerated(true); + $this->changes[Level::chunkHash($chunkX, $chunkZ)] = $chunk; } public function setChunkPopulated($chunkX, $chunkZ){ - $this->getChunk($chunkX, $chunkZ)->setPopulated(true); + $chunk = $this->getChunk($chunkX, $chunkZ); + $chunk->setPopulated(true); + $this->changes[Level::chunkHash($chunkX, $chunkZ)] = $chunk; } protected function requestChunk($chunkX, $chunkZ){ @@ -166,11 +168,12 @@ class GenerationChunkManager implements ChunkManager{ /** * @param int $chunkX * @param int $chunkZ - * @param SimpleChunk $chunk + * @param FullChunk $chunk */ - public function setChunk($chunkX, $chunkZ, SimpleChunk $chunk){ - $this->chunks[Level::chunkHash($chunkX, $chunkZ)] = $chunk; - if($chunk->isGenerated() and $chunk->isPopulated()){ + public function setChunk($chunkX, $chunkZ, FullChunk $chunk){ + $this->chunks[$index = Level::chunkHash($chunkX, $chunkZ)] = $chunk; + $this->changes[$index] = $chunk; + if($chunk->isPopulated()){ //TODO: Queue to be sent } } diff --git a/src/pocketmine/level/generator/GenerationManager.php b/src/pocketmine/level/generator/GenerationManager.php index 98e58739d..d945ad988 100644 --- a/src/pocketmine/level/generator/GenerationManager.php +++ b/src/pocketmine/level/generator/GenerationManager.php @@ -21,7 +21,7 @@ namespace pocketmine\level\generator; -use pocketmine\level\format\SimpleChunk; +use pocketmine\level\format\FullChunk; use pocketmine\level\Level; use pocketmine\utils\Binary; @@ -60,7 +60,8 @@ class GenerationManager{ * int32 levelID * int32 chunkX * int32 chunkZ - * byte flags (1 generated, 2 populated) + * byte className length + * byte[] className * byte[] chunk (none if generated flag is not set) */ const PACKET_SEND_CHUNK = 0x02; @@ -144,10 +145,11 @@ class GenerationManager{ if(isset($this->levels[$levelID])){ $this->generatedQueue[$levelID][$index] = true; if(count($this->generatedQueue[$levelID]) > 6){ - $this->levels[$levelID]->doGarbageCollection(); - foreach($this->levels[$levelID]->getChangedChunks(true) as $chunk){ + foreach($this->levels[$levelID]->getChangedChunks() as $chunk){ $this->sendChunk($levelID, $chunk); } + $this->levels[$levelID]->doGarbageCollection(); + $this->levels[$levelID]->cleanChangedChunks(); $this->generatedQueue[$levelID] = []; } } @@ -166,7 +168,7 @@ class GenerationManager{ $this->requestQueue->enqueue([$levelID, $chunkX, $chunkZ]); } - protected function receiveChunk($levelID, SimpleChunk $chunk){ + protected function receiveChunk($levelID, FullChunk $chunk){ if($this->needsChunk !== null and $this->needsChunk[0] === $levelID){ if($this->needsChunk[1] === $chunk->getX() and $this->needsChunk[2] === $chunk->getZ()){ $this->needsChunk = $chunk; @@ -180,7 +182,7 @@ class GenerationManager{ * @param $chunkX * @param $chunkZ * - * @return SimpleChunk + * @return FullChunk */ public function requestChunk($levelID, $chunkX, $chunkZ){ $this->needsChunk = [$levelID, $chunkX, $chunkZ]; @@ -188,19 +190,19 @@ class GenerationManager{ @socket_write($this->socket, Binary::writeInt(strlen($binary)) . $binary); do{ $this->readPacket(); - }while($this->shutdown !== true and !($this->needsChunk instanceof SimpleChunk)); + }while($this->shutdown !== true and !($this->needsChunk instanceof FullChunk)); $chunk = $this->needsChunk; $this->needsChunk = null; - if($chunk instanceof SimpleChunk){ + if($chunk instanceof FullChunk){ return $chunk; }else{ - return new SimpleChunk($chunkX, $chunkZ, 0); + return null; } } - public function sendChunk($levelID, SimpleChunk $chunk){ - $binary = chr(self::PACKET_SEND_CHUNK) . Binary::writeInt($levelID) . $chunk->toBinary(); + public function sendChunk($levelID, FullChunk $chunk){ + $binary = chr(self::PACKET_SEND_CHUNK) . Binary::writeInt($levelID) . chr(strlen($class = get_class($chunk))) . $class . $chunk->toBinary(); @socket_write($this->socket, Binary::writeInt(strlen($binary)) . $binary); } @@ -234,7 +236,11 @@ class GenerationManager{ }elseif($pid === self::PACKET_SEND_CHUNK){ $levelID = Binary::readInt(substr($packet, $offset, 4)); $offset += 4; - $chunk = SimpleChunk::fromBinary(substr($packet, $offset)); + $len = ord($packet{$offset++}); + /** @var FullChunk $class */ + $class = substr($packet, $offset, $len); + $offset += $len; + $chunk = $class::fromBinary(substr($packet, $offset)); $this->receiveChunk($levelID, $chunk); }elseif($pid === self::PACKET_OPEN_LEVEL){ $levelID = Binary::readInt(substr($packet, $offset, 4)); diff --git a/src/pocketmine/level/generator/GenerationRequestManager.php b/src/pocketmine/level/generator/GenerationRequestManager.php index 378f5b0b9..f8029bbe3 100644 --- a/src/pocketmine/level/generator/GenerationRequestManager.php +++ b/src/pocketmine/level/generator/GenerationRequestManager.php @@ -21,7 +21,7 @@ namespace pocketmine\level\generator; -use pocketmine\level\format\SimpleChunk; +use pocketmine\level\format\FullChunk; use pocketmine\level\Level; use pocketmine\Server; use pocketmine\utils\Binary; @@ -77,8 +77,8 @@ class GenerationRequestManager{ return $buffer; } - protected function sendChunk($levelID, SimpleChunk $chunk){ - $binary = chr(GenerationManager::PACKET_SEND_CHUNK) . Binary::writeInt($levelID) . $chunk->toBinary(); + protected function sendChunk($levelID, FullChunk $chunk){ + $binary = chr(GenerationManager::PACKET_SEND_CHUNK) . Binary::writeInt($levelID) . chr(strlen($class = get_class($chunk))) . $class . $chunk->toBinary(); @socket_write($this->socket, Binary::writeInt(strlen($binary)) . $binary); } @@ -89,14 +89,14 @@ class GenerationRequestManager{ protected function handleRequest($levelID, $chunkX, $chunkZ){ if(($level = $this->server->getLevel($levelID)) instanceof Level){ - $this->sendChunk($levelID, $level->getChunk($chunkX, $chunkZ, false)); + $this->sendChunk($levelID, $level->getChunkAt($chunkX, $chunkZ, true)); }else{ $buffer = chr(GenerationManager::PACKET_CLOSE_LEVEL) . Binary::writeInt($levelID); @socket_write($this->socket, Binary::writeInt(strlen($buffer)) . $buffer); } } - protected function receiveChunk($levelID, SimpleChunk $chunk){ + protected function receiveChunk($levelID, FullChunk $chunk){ if(($level = $this->server->getLevel($levelID)) instanceof Level){ $level->generateChunkCallback($chunk->getX(), $chunk->getZ(), $chunk); }else{ @@ -106,10 +106,11 @@ class GenerationRequestManager{ } public function handlePackets(){ - if(($len = @socket_read($this->socket, 4)) !== false){ + if(($len = @socket_read($this->socket, 4)) !== false and $len !== ""){ if(strlen($len) < 4){ $len .= $this->socketRead(4 - strlen($len)); } + $packet = $this->socketRead(Binary::readInt($len)); $pid = ord($packet{0}); $offset = 1; @@ -124,7 +125,11 @@ class GenerationRequestManager{ }elseif($pid === GenerationManager::PACKET_SEND_CHUNK){ $levelID = Binary::readInt(substr($packet, $offset, 4)); $offset += 4; - $chunk = SimpleChunk::fromBinary(substr($packet, $offset)); + $len = ord($packet{$offset++}); + /** @var FullChunk $class */ + $class = substr($packet, $offset, $len); + $offset += $len; + $chunk = $class::fromBinary(substr($packet, $offset)); $this->receiveChunk($levelID, $chunk); } } diff --git a/src/pocketmine/level/generator/GenerationThread.php b/src/pocketmine/level/generator/GenerationThread.php index d9ae29816..1c4dca32f 100644 --- a/src/pocketmine/level/generator/GenerationThread.php +++ b/src/pocketmine/level/generator/GenerationThread.php @@ -87,6 +87,7 @@ class GenerationThread extends \Thread{ } public function run(){ + error_reporting(-1); //Load removed dependencies, can't use require_once() foreach($this->loadPaths as $name => $path){ if(!class_exists($name, false) and !class_exists($name, false)){ From cb4a970631ec735e325ee308734d8302e453aae4 Mon Sep 17 00:00:00 2001 From: Shoghi Cervantes Date: Thu, 31 Jul 2014 19:27:01 +0200 Subject: [PATCH 13/13] Tuned generator to use a better way to process chunks, closes #1807 #1794 #1740 #1741 #1685 --- src/pocketmine/Player.php | 4 +-- src/pocketmine/level/Level.php | 3 +- src/pocketmine/level/format/anvil/Anvil.php | 33 +++++++------------ src/pocketmine/level/format/anvil/Chunk.php | 6 ++++ .../level/format/anvil/RegionLoader.php | 6 ---- .../level/format/mcregion/Chunk.php | 8 +++++ .../level/format/mcregion/McRegion.php | 14 ++++---- .../level/format/mcregion/RegionLoader.php | 3 +- src/pocketmine/level/generator/Flat.php | 1 + .../generator/GenerationChunkManager.php | 6 ++-- 10 files changed, 43 insertions(+), 41 deletions(-) diff --git a/src/pocketmine/Player.php b/src/pocketmine/Player.php index d18da0889..4e010ca7f 100644 --- a/src/pocketmine/Player.php +++ b/src/pocketmine/Player.php @@ -520,7 +520,7 @@ class Player extends Human implements CommandSender, InventoryHolder, IPlayer{ return $this->spawnPosition; }else{ $level = $this->server->getDefaultLevel(); - return $level->getSpawn(); + return $level->getSafeSpawn(); } } @@ -1740,7 +1740,7 @@ class Player extends Human implements CommandSender, InventoryHolder, IPlayer{ $this->craftingType = 0; - $this->server->getPluginManager()->callEvent($ev = new PlayerRespawnEvent($this, $this->spawnPosition)); + $this->server->getPluginManager()->callEvent($ev = new PlayerRespawnEvent($this, $this->getSpawn())); $this->teleport($ev->getRespawnPosition()); //$this->entity->fire = 0; diff --git a/src/pocketmine/level/Level.php b/src/pocketmine/level/Level.php index 75e90532b..818550167 100644 --- a/src/pocketmine/level/Level.php +++ b/src/pocketmine/level/Level.php @@ -1635,9 +1635,10 @@ class Level implements ChunkManager, Metadatable{ * Sets the spawnpoint * * @param Vector3 $pos + * @deprecated */ public function setSpawn(Vector3 $pos){ - $this->provider->setSpawn($pos); + $this->setSpawnLocation($pos); } /** diff --git a/src/pocketmine/level/format/anvil/Anvil.php b/src/pocketmine/level/format/anvil/Anvil.php index 9781feb9d..a8af132dc 100644 --- a/src/pocketmine/level/format/anvil/Anvil.php +++ b/src/pocketmine/level/format/anvil/Anvil.php @@ -67,24 +67,16 @@ class Anvil extends McRegion{ return new ChunkRequestTask($this, $this->getLevel()->getID(), $x, $z); } - public function loadChunk($chunkX, $chunkZ, $create = false){ - $index = Level::chunkHash($chunkX, $chunkZ); - if(isset($this->chunks[$index])){ - return true; - } - $regionX = $regionZ = null; - self::getRegionIndex($chunkX, $chunkZ, $regionX, $regionZ); - $this->loadRegion($regionX, $regionZ); + /** + * @param $x + * @param $z + * + * @return RegionLoader + */ + protected function getRegion($x, $z){ + $index = $x . ":" . $z; - $this->level->timings->syncChunkLoadDataTimer->startTiming(); - $chunk = $this->getRegion($regionX, $regionZ)->readChunk($chunkX - $regionX * 32, $chunkZ - $regionZ * 32, $create); //generate empty chunk if not loaded - $this->level->timings->syncChunkLoadDataTimer->stopTiming(); - - if($chunk instanceof Chunk){ - $this->chunks[$index] = $chunk; - }else{ - return false; - } + return isset($this->regions[$index]) ? $this->regions[$index] : null; } /** @@ -112,10 +104,9 @@ class Anvil extends McRegion{ $region->removeChunk($chunkX - $region->getX() * 32, $chunkZ - $region->getZ() * 32); $this->loadChunk($chunkX, $chunkZ); }else{ - $newChunk = clone $chunk; - $newChunk->setX($chunkX); - $newChunk->setZ($chunkZ); - $this->chunks[Level::chunkHash($chunkX, $chunkZ)] = $newChunk; + $chunk->setX($chunkX); + $chunk->setZ($chunkZ); + $this->chunks[Level::chunkHash($chunkX, $chunkZ)] = $chunk; //$this->saveChunk($chunkX, $chunkZ); } } diff --git a/src/pocketmine/level/format/anvil/Chunk.php b/src/pocketmine/level/format/anvil/Chunk.php index 77125e97f..d3491c0c0 100644 --- a/src/pocketmine/level/format/anvil/Chunk.php +++ b/src/pocketmine/level/format/anvil/Chunk.php @@ -29,6 +29,7 @@ use pocketmine\nbt\tag\Byte; use pocketmine\nbt\tag\ByteArray; use pocketmine\nbt\tag\Compound; use pocketmine\nbt\tag\Enum; +use pocketmine\nbt\tag\Int; use pocketmine\nbt\tag\IntArray; use pocketmine\Player; use pocketmine\utils\Binary; @@ -93,6 +94,8 @@ class Chunk extends BaseChunk{ } parent::__construct($level, $this->nbt["xPos"], $this->nbt["zPos"], $sections, $this->nbt->Biomes->getValue(), $this->nbt->BiomeColors->getValue(), $this->nbt->Entities->getValue(), $this->nbt->TileEntities->getValue()); + + unset($this->nbt->Sections); } /** @@ -151,6 +154,9 @@ class Chunk extends BaseChunk{ public function toBinary(){ $nbt = $this->getNBT(); + $nbt->xPos = new Int("xPos", $this->x); + $nbt->zPos = new Int("zPos", $this->z); + $nbt->Sections = new Enum("Sections", []); $nbt->Sections->setTagType(NBT::TAG_Compound); foreach($this->getSections() as $section){ diff --git a/src/pocketmine/level/format/anvil/RegionLoader.php b/src/pocketmine/level/format/anvil/RegionLoader.php index c8a2885c2..f1c93fc84 100644 --- a/src/pocketmine/level/format/anvil/RegionLoader.php +++ b/src/pocketmine/level/format/anvil/RegionLoader.php @@ -22,7 +22,6 @@ namespace pocketmine\level\format\anvil; use pocketmine\level\format\LevelProvider; -use pocketmine\level\format\mcregion\Chunk; use pocketmine\nbt\NBT; use pocketmine\nbt\tag\Byte; use pocketmine\nbt\tag\ByteArray; @@ -31,7 +30,6 @@ use pocketmine\nbt\tag\Enum; use pocketmine\nbt\tag\Int; use pocketmine\nbt\tag\IntArray; use pocketmine\nbt\tag\Long; -use pocketmine\Player; use pocketmine\utils\Binary; class RegionLoader extends \pocketmine\level\format\mcregion\RegionLoader{ @@ -130,8 +128,4 @@ class RegionLoader extends \pocketmine\level\format\mcregion\RegionLoader{ $this->saveChunk($x, $z, $chunkData); } - public function writeChunk(Chunk $chunk){ - $this->saveChunk($chunk->getX() - ($this->getX() * 32), $chunk->getZ() - ($this->getZ() * 32), $chunk->toBinary()); - } - } \ 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 d5ba9054d..3fd119c61 100644 --- a/src/pocketmine/level/format/mcregion/Chunk.php +++ b/src/pocketmine/level/format/mcregion/Chunk.php @@ -28,6 +28,7 @@ use pocketmine\nbt\tag\Byte; use pocketmine\nbt\tag\ByteArray; use pocketmine\nbt\tag\Compound; use pocketmine\nbt\tag\Enum; +use pocketmine\nbt\tag\Int; use pocketmine\nbt\tag\IntArray; use pocketmine\Player; use pocketmine\utils\Binary; @@ -70,6 +71,10 @@ class Chunk extends BaseFullChunk{ } parent::__construct($level, $this->nbt["xPos"], $this->nbt["zPos"], $this->nbt["Blocks"], $this->nbt["Data"], $this->nbt["SkyLight"], $this->nbt["BlockLight"], $this->nbt->Biomes->getValue(), $this->nbt->BiomeColors->getValue(), $this->nbt->Entities->getValue(), $this->nbt->TileEntities->getValue()); + unset($this->nbt->Blocks); + unset($this->nbt->Data); + unset($this->nbt->SkyLight); + unset($this->nbt->BlockLight); } public function getBlockId($x, $y, $z){ @@ -245,6 +250,9 @@ class Chunk extends BaseFullChunk{ public function toBinary(){ $nbt = $this->getNBT(); + $nbt->xPos = new Int("xPos", $this->x); + $nbt->zPos = new Int("zPos", $this->z); + $nbt->Blocks = new ByteArray("Blocks", $this->getBlockIdArray()); $nbt->Data = new ByteArray("Data", $this->getBlockDataArray()); $nbt->SkyLight = new ByteArray("SkyLight", $this->getBlockSkyLightArray()); diff --git a/src/pocketmine/level/format/mcregion/McRegion.php b/src/pocketmine/level/format/mcregion/McRegion.php index 1c4b0c13d..7fca5c3b0 100644 --- a/src/pocketmine/level/format/mcregion/McRegion.php +++ b/src/pocketmine/level/format/mcregion/McRegion.php @@ -142,12 +142,11 @@ class McRegion extends BaseLevelProvider{ $regionX = $regionZ = null; self::getRegionIndex($chunkX, $chunkZ, $regionX, $regionZ); $this->loadRegion($regionX, $regionZ); - $this->level->timings->syncChunkLoadDataTimer->startTiming(); $chunk = $this->getRegion($regionX, $regionZ)->readChunk($chunkX - $regionX * 32, $chunkZ - $regionZ * 32, $create); //generate empty chunk if not loaded $this->level->timings->syncChunkLoadDataTimer->stopTiming(); - if($chunk instanceof Chunk){ + if($chunk instanceof FullChunk){ $this->chunks[$index] = $chunk; }else{ return false; @@ -156,7 +155,7 @@ class McRegion extends BaseLevelProvider{ public function unloadChunk($x, $z, $safe = true){ $chunk = $this->getChunk($x, $z, false); - if($chunk instanceof Chunk){ + if($chunk instanceof FullChunk){ if($safe === true and $this->isChunkLoaded($x, $z)){ foreach($chunk->getEntities() as $entity){ if($entity instanceof Player){ @@ -233,10 +232,9 @@ class McRegion extends BaseLevelProvider{ $region->removeChunk($chunkX - $region->getX() * 32, $chunkZ - $region->getZ() * 32); $this->loadChunk($chunkX, $chunkZ); }else{ - $newChunk = clone $chunk; - $newChunk->setX($chunkX); - $newChunk->setZ($chunkZ); - $this->chunks[Level::chunkHash($chunkX, $chunkZ)] = $newChunk; + $chunk->setX($chunkX); + $chunk->setZ($chunkZ); + $this->chunks[Level::chunkHash($chunkX, $chunkZ)] = $chunk; //$this->saveChunk($chunkX, $chunkZ); } } @@ -255,7 +253,7 @@ class McRegion extends BaseLevelProvider{ public function isChunkPopulated($chunkX, $chunkZ){ $chunk = $this->getChunk($chunkX, $chunkZ); - if($chunk instanceof Chunk){ + if($chunk instanceof FullChunk){ return $chunk->isPopulated(); }else{ return false; diff --git a/src/pocketmine/level/format/mcregion/RegionLoader.php b/src/pocketmine/level/format/mcregion/RegionLoader.php index a15299abb..0dcb92e86 100644 --- a/src/pocketmine/level/format/mcregion/RegionLoader.php +++ b/src/pocketmine/level/format/mcregion/RegionLoader.php @@ -21,6 +21,7 @@ namespace pocketmine\level\format\mcregion; +use pocketmine\level\format\FullChunk; use pocketmine\level\format\LevelProvider; use pocketmine\nbt\NBT; use pocketmine\nbt\tag\Byte; @@ -180,7 +181,7 @@ class RegionLoader{ $this->locationTable[$index][1] = 0; } - public function writeChunk(Chunk $chunk){ + public function writeChunk(FullChunk $chunk){ $this->saveChunk($chunk->getX() - ($this->getX() * 32), $chunk->getZ() - ($this->getZ() * 32), $chunk->toBinary()); } diff --git a/src/pocketmine/level/generator/Flat.php b/src/pocketmine/level/generator/Flat.php index 9c03a9dda..9480622be 100644 --- a/src/pocketmine/level/generator/Flat.php +++ b/src/pocketmine/level/generator/Flat.php @@ -122,6 +122,7 @@ class Flat extends Generator{ } } + preg_match_all('#(([0-9a-z_]{1,})\(?([0-9a-z_ =:]{0,})\)?),?#', $options, $matches); foreach($matches[2] as $i => $option){ $params = true; diff --git a/src/pocketmine/level/generator/GenerationChunkManager.php b/src/pocketmine/level/generator/GenerationChunkManager.php index eba2e89cf..195ca5588 100644 --- a/src/pocketmine/level/generator/GenerationChunkManager.php +++ b/src/pocketmine/level/generator/GenerationChunkManager.php @@ -85,6 +85,7 @@ class GenerationChunkManager implements ChunkManager{ $index = Level::chunkHash($chunkX, $chunkZ); $chunk = !isset($this->chunks[$index]) ? $this->requestChunk($chunkX, $chunkZ) : $this->chunks[$index]; $this->unloadQueue->detach($chunk); + $this->changes[$index] = $chunk; return $chunk; } @@ -112,6 +113,9 @@ class GenerationChunkManager implements ChunkManager{ } foreach($this->chunks as $chunk){ + if(isset($this->changes[$index = Level::chunkHash($chunk->getX(), $chunk->getZ())])){ + continue; + } $this->unloadQueue->attach($chunk); } } @@ -150,13 +154,11 @@ class GenerationChunkManager implements ChunkManager{ public function setChunkGenerated($chunkX, $chunkZ){ $chunk = $this->getChunk($chunkX, $chunkZ); $chunk->setGenerated(true); - $this->changes[Level::chunkHash($chunkX, $chunkZ)] = $chunk; } public function setChunkPopulated($chunkX, $chunkZ){ $chunk = $this->getChunk($chunkX, $chunkZ); $chunk->setPopulated(true); - $this->changes[Level::chunkHash($chunkX, $chunkZ)] = $chunk; } protected function requestChunk($chunkX, $chunkZ){