Bump to API 3.0.0-ALPHA2 - READ DESCRIPTION!

Refactored level\format\generic\GenericChunk -> level\format\Chunk.
Re-added support for async chunk sending
Refactored most Level IO into new namespaces for more organisation
Removed LevelDB loader completely (will be re-added at a later date)
This commit is contained in:
Dylan K. Taylor 2017-01-06 17:13:45 +00:00
parent d8908676ac
commit ad0553fbf8
23 changed files with 795 additions and 1952 deletions

View File

@ -75,7 +75,7 @@ namespace pocketmine {
use raklib\RakLib;
const VERSION = "1.6.2dev";
const API_VERSION = "3.0.0-ALPHA1";
const API_VERSION = "3.0.0-ALPHA2";
const CODENAME = "Unleashed";
/*

View File

@ -50,11 +50,10 @@ use pocketmine\inventory\ShapelessRecipe;
use pocketmine\item\enchantment\Enchantment;
use pocketmine\item\Item;
use pocketmine\lang\BaseLang;
use pocketmine\level\format\leveldb\LevelDB;
use pocketmine\level\format\LevelProviderManager;
use pocketmine\level\format\region\Anvil;
use pocketmine\level\format\region\McRegion;
use pocketmine\level\format\region\PMAnvil;
use pocketmine\level\format\io\LevelProviderManager;
use pocketmine\level\format\io\region\Anvil;
use pocketmine\level\format\io\region\McRegion;
use pocketmine\level\format\io\region\PMAnvil;
use pocketmine\level\generator\biome\Biome;
use pocketmine\level\generator\Flat;
use pocketmine\level\generator\Generator;
@ -107,6 +106,8 @@ use pocketmine\utils\Utils;
use pocketmine\utils\UUID;
use pocketmine\utils\VersionString;
//use pocketmine\level\format\leveldb\LevelDB;
/**
* The class that manages everything
*/
@ -1013,7 +1014,7 @@ class Server{
try{
$path = $this->getDataPath() . "worlds/" . $name . "/";
/** @var \pocketmine\level\format\LevelProvider $provider */
/** @var \pocketmine\level\format\io\LevelProvider $provider */
$provider::generate($path, $name, $seed, $generator, $options);
$level = new Level($this, $name, $path, $provider);
@ -1518,10 +1519,10 @@ class Server{
LevelProviderManager::addProvider(Anvil::class);
LevelProviderManager::addProvider(McRegion::class);
LevelProviderManager::addProvider(PMAnvil::class);
if(extension_loaded("leveldb")){
/*if(extension_loaded("leveldb")){
$this->logger->debug($this->getLanguage()->translateString("pocketmine.debug.enable"));
LevelProviderManager::addProvider(LevelDB::class);
}
}*/
Generator::addGenerator(Flat::class, "flat");

View File

@ -24,15 +24,11 @@ namespace pocketmine\block;
use pocketmine\item\Item;
use pocketmine\level\Level;
use pocketmine\nbt\tag\{
ByteTag,
CompoundTag,
FloatTag,
IntTag,
StringTag
ByteTag, CompoundTag, FloatTag, IntTag, StringTag
};
use pocketmine\Player;
use pocketmine\tile\Tile;
use pocketmine\tile\ItemFrame as TileItemFrame;
use pocketmine\tile\Tile;
class ItemFrame extends Flowable{
protected $id = Block::ITEM_FRAME_BLOCK;

View File

@ -66,8 +66,8 @@ use pocketmine\event\Timings;
use pocketmine\inventory\InventoryHolder;
use pocketmine\item\Item;
use pocketmine\level\format\Chunk;
use pocketmine\level\format\generic\BaseLevelProvider;
use pocketmine\level\format\LevelProvider;
use pocketmine\level\format\io\BaseLevelProvider;
use pocketmine\level\format\io\LevelProvider;
use pocketmine\level\generator\GenerationTask;
use pocketmine\level\generator\Generator;
use pocketmine\level\generator\GeneratorRegisterTask;

File diff suppressed because it is too large Load Diff

View File

@ -21,7 +21,7 @@
declare(strict_types = 1);
namespace pocketmine\level\format\generic;
namespace pocketmine\level\format;
class EmptySubChunk extends SubChunk{

View File

@ -21,7 +21,7 @@
declare(strict_types = 1);
namespace pocketmine\level\format\generic;
namespace pocketmine\level\format;
class SubChunk{

View File

@ -1,751 +0,0 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
/**
* Implementation of MCPE-style chunks with subchunks with XZY ordering.
*/
declare(strict_types = 1);
namespace pocketmine\level\format\generic;
use pocketmine\block\Block;
use pocketmine\entity\Entity;
use pocketmine\level\format\Chunk;
use pocketmine\level\format\LevelProvider;
use pocketmine\nbt\NBT;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\Player;
use pocketmine\tile\Tile;
use pocketmine\tile\Spawnable;
use pocketmine\utils\BinaryStream;
use pocketmine\utils\ChunkException;
class GenericChunk implements Chunk{
/** @var LevelProvider */
protected $provider;
protected $x;
protected $z;
protected $hasChanged = false;
protected $isInit = false;
protected $lightPopulated = false;
protected $terrainGenerated = false;
protected $terrainPopulated = false;
protected $height = Chunk::MAX_SUBCHUNKS;
/** @var SubChunk[] */
protected $subChunks = [];
/** @var EmptySubChunk */
protected $emptySubChunk = null;
/** @var Tile[] */
protected $tiles = [];
protected $tileList = [];
/** @var Entity[] */
protected $entities = [];
/** @var int[256] */
protected $heightMap = [];
/** @var string */
protected $biomeIds;
protected $extraData = [];
/** @var CompoundTag[] */
protected $NBTtiles = [];
/** @var CompoundTag[] */
protected $NBTentities = [];
/**
* @param LevelProvider $provider
* @param int $chunkX
* @param int $chunkZ
* @param SubChunk[] $subChunks
* @param CompoundTag[] $entities
* @param CompoundTag[] $tiles
* @param string $biomeIds
* @param int[] $heightMap
*/
public function __construct($provider, int $chunkX, int $chunkZ, array $subChunks = [], array $entities = [], array $tiles = [], string $biomeIds = "", array $heightMap = []){
$this->provider = $provider;
$this->x = $chunkX;
$this->z = $chunkZ;
$this->height = $provider !== null ? ($provider->getWorldHeight() >> 4) : 16;
$this->emptySubChunk = new EmptySubChunk();
foreach($subChunks as $y => $subChunk){
if($y < 0 or $y >= $this->height){
throw new ChunkException("Invalid subchunk index $y!");
}
if($subChunk->isEmpty()){
$this->subChunks[$y] = $this->emptySubChunk;
}else{
$this->subChunks[$y] = $subChunk;
}
}
for($i = 0; $i < $this->height; ++$i){
if(!isset($this->subChunks[$i])){
$this->subChunks[$i] = $this->emptySubChunk;
}
}
if(count($heightMap) === 256){
$this->heightMap = $heightMap;
}else{
assert(count($heightMap) === 0, "Wrong HeightMap value count, expected 256, got " . count($heightMap));
$val = ($this->height * 16) - 1;
$this->heightMap = array_fill(0, 256, $val);
}
if(strlen($biomeIds) === 256){
$this->biomeIds = $biomeIds;
}else{
assert(strlen($biomeIds) === 0, "Wrong BiomeIds value count, expected 256, got " . strlen($biomeIds));
$this->biomeIds = str_repeat("\x00", 256);
}
$this->NBTtiles = $tiles;
$this->NBTentities = $entities;
}
public function getX() : int{
return $this->x;
}
public function getZ() : int{
return $this->z;
}
public function setX(int $x){
$this->x = $x;
}
public function setZ(int $z){
$this->z = $z;
}
public function getProvider(){
return $this->provider;
}
public function setProvider(LevelProvider $provider){
$this->provider = $provider;
}
public function getHeight() : int{
return $this->height;
}
public function getFullBlock(int $x, int $y, int $z) : int{
return $this->getSubChunk($y >> 4)->getFullBlock($x, $y & 0x0f, $z);
}
public function setBlock(int $x, int $y, int $z, $blockId = null, $meta = null) : bool{
if($this->getSubChunk($y >> 4, true)->setBlock($x, $y & 0x0f, $z, $blockId !== null ? ($blockId & 0xff) : null, $meta !== null ? ($meta & 0x0f) : null)){
$this->hasChanged = true;
return true;
}
return false;
}
public function getBlockId(int $x, int $y, int $z) : int{
return $this->getSubChunk($y >> 4)->getBlockId($x, $y & 0x0f, $z);
}
public function setBlockId(int $x, int $y, int $z, int $id){
if($this->getSubChunk($y >> 4, true)->setBlockId($x, $y & 0x0f, $z, $id)){
$this->hasChanged = true;
}
}
public function getBlockData(int $x, int $y, int $z) : int{
return $this->getSubChunk($y >> 4)->getBlockData($x, $y & 0x0f, $z);
}
public function setBlockData(int $x, int $y, int $z, int $data){
if($this->getSubChunk($y >> 4)->setBlockData($x, $y & 0x0f, $z, $data)){
$this->hasChanged = true;
}
}
public function getBlockExtraData(int $x, int $y, int $z) : int{
return $this->extraData[GenericChunk::chunkBlockHash($x, $y, $z)] ?? 0;
}
public function setBlockExtraData(int $x, int $y, int $z, int $data){
if($data === 0){
unset($this->extraData[GenericChunk::chunkBlockHash($x, $y, $z)]);
}else{
$this->extraData[GenericChunk::chunkBlockHash($x, $y, $z)] = $data;
}
$this->hasChanged = true;
}
public function getBlockSkyLight(int $x, int $y, int $z) : int{
return $this->getSubChunk($y >> 4)->getBlockSkyLight($x, $y & 0x0f, $z);
}
public function setBlockSkyLight(int $x, int $y, int $z, int $level){
if($this->getSubChunk($y >> 4)->setBlockSkyLight($x, $y & 0x0f, $z, $level)){
$this->hasChanged = true;
}
}
public function getBlockLight(int $x, int $y, int $z) : int{
return $this->getSubChunk($y >> 4)->getBlockLight($x, $y & 0x0f, $z);
}
public function setBlockLight(int $x, int $y, int $z, int $level){
if($this->getSubChunk($y >> 4)->setBlockLight($x, $y & 0x0f, $z, $level)){
$this->hasChanged = true;
}
}
public function getHighestBlockAt(int $x, int $z, bool $useHeightMap = true) : int{
if($useHeightMap){
$height = $this->getHeightMap($x, $z);
if($height !== 0 and $height !== 255){
return $height;
}
}
$index = $this->getHighestSubChunkIndex();
if($index < 0){
return 0;
}
$height = $index << 4;
for($y = $index; $y >= 0; --$y){
$height = $this->getSubChunk($y)->getHighestBlockAt($x, $z) | ($y << 4);
if($height !== -1){
break;
}
}
$this->setHeightMap($x, $z, $height);
return $height;
}
public function getHeightMap(int $x, int $z) : int{
return $this->heightMap[($z << 4) | $x];
}
public function setHeightMap(int $x, int $z, int $value){
$this->heightMap[($z << 4) | $x] = $value;
}
public function recalculateHeightMap(){
for($z = 0; $z < 16; ++$z){
for($x = 0; $x < 16; ++$x){
$this->setHeightMap($x, $z, $this->getHighestBlockAt($x, $z, false));
}
}
}
public function populateSkyLight(){
//TODO: rewrite this, use block light filters and diffusion, actual proper sky light population
for($x = 0; $x < 16; ++$x){
for($z = 0; $z < 16; ++$z){
$heightMap = $this->getHeightMap($x, $z);
$y = min(($this->getHighestSubChunkIndex() + 1) << 4, $heightMap);
for(; $y > $heightMap; --$y){
$this->setBlockSkyLight($x, $y, $z, 15);
}
for(; $y > 0; --$y){
if($this->getBlockId($x, $y, $z) !== Block::AIR){
break;
}
$this->setBlockSkyLight($x, $y, $z, 15);
}
$this->setHeightMap($x, $z, $y);
}
}
}
public function getBiomeId(int $x, int $z) : int{
return ord($this->biomeIds{($z << 4) | $x});
}
public function setBiomeId(int $x, int $z, int $biomeId){
$this->hasChanged = true;
$this->biomeIds{($z << 4) | $x} = chr($biomeId & 0xff);
}
public function getBlockIdColumn(int $x, int $z) : string{
$result = "";
foreach($this->subChunks as $subChunk){
$result .= $subChunk->getBlockIdColumn($x, $z);
}
return $result;
}
public function getBlockDataColumn(int $x, int $z) : string{
$result = "";
foreach($this->subChunks as $subChunk){
$result .= $subChunk->getBlockDataColumn($x, $z);
}
return $result;
}
public function getBlockSkyLightColumn(int $x, int $z) : string{
$result = "";
foreach($this->subChunks as $subChunk){
$result .= $subChunk->getSkyLightColumn($x, $z);
}
return $result;
}
public function getBlockLightColumn(int $x, int $z) : string{
$result = "";
foreach($this->subChunks as $subChunk){
$result .= $subChunk->getBlockLightColumn($x, $z);
}
return $result;
}
public function isLightPopulated() : bool{
return $this->lightPopulated;
}
public function setLightPopulated(bool $value = true){
$this->lightPopulated = $value;
}
public function isPopulated() : bool{
return $this->terrainPopulated;
}
public function setPopulated(bool $value = true){
$this->terrainPopulated = $value;
}
public function isGenerated() : bool{
return $this->terrainGenerated;
}
public function setGenerated(bool $value = true){
$this->terrainGenerated = $value;
}
public function addEntity(Entity $entity){
$this->entities[$entity->getId()] = $entity;
if(!($entity instanceof Player) and $this->isInit){
$this->hasChanged = true;
}
}
public function removeEntity(Entity $entity){
unset($this->entities[$entity->getId()]);
if(!($entity instanceof Player) and $this->isInit){
$this->hasChanged = true;
}
}
public function addTile(Tile $tile){
$this->tiles[$tile->getId()] = $tile;
if(isset($this->tileList[$index = (($tile->x & 0x0f) << 12) | (($tile->z & 0x0f) << 8) | ($tile->y & 0xff)]) and $this->tileList[$index] !== $tile){
$this->tileList[$index]->close();
}
$this->tileList[$index] = $tile;
if($this->isInit){
$this->hasChanged = true;
}
}
public function removeTile(Tile $tile){
unset($this->tiles[$tile->getId()]);
unset($this->tileList[(($tile->x & 0x0f) << 12) | (($tile->z & 0x0f) << 8) | ($tile->y & 0xff)]);
if($this->isInit){
$this->hasChanged = true;
}
}
public function getEntities() : array{
return $this->entities;
}
public function getTiles() : array{
return $this->tiles;
}
public function getTile(int $x, int $y, int $z){
$index = ($x << 12) | ($z << 8) | $y;
return $this->tileList[$index] ?? null;
}
public function isLoaded() : bool{
return $this->getProvider() === null ? false : $this->getProvider()->isChunkLoaded($this->getX(), $this->getZ());
}
public function load(bool $generate = true) : bool{
return $this->getProvider() === null ? false : $this->getProvider()->getChunk($this->getX(), $this->getZ(), true) instanceof GenericChunk;
}
public function unload(bool $save = true, bool $safe = true) : bool{
$level = $this->getProvider();
if($level === null){
return true;
}
if($save === true and $this->hasChanged){
$level->saveChunk($this->getX(), $this->getZ());
}
if($safe === true){
foreach($this->getEntities() as $entity){
if($entity instanceof Player){
return false;
}
}
}
foreach($this->getEntities() as $entity){
if($entity instanceof Player){
continue;
}
$entity->close();
}
foreach($this->getTiles() as $tile){
$tile->close();
}
$this->provider = null;
return true;
}
public function initChunk(){
if($this->getProvider() instanceof LevelProvider and !$this->isInit){
$changed = false;
if($this->NBTentities !== null){
$this->getProvider()->getLevel()->timings->syncChunkLoadEntitiesTimer->startTiming();
foreach($this->NBTentities as $nbt){
if($nbt instanceof CompoundTag){
if(!isset($nbt->id)){
$changed = true;
continue;
}
if(($nbt["Pos"][0] >> 4) !== $this->x or ($nbt["Pos"][2] >> 4) !== $this->z){
$changed = true;
continue; //Fixes entities allocated in wrong chunks.
}
if(($entity = Entity::createEntity($nbt["id"], $this, $nbt)) instanceof Entity){
$entity->spawnToAll();
}else{
$changed = true;
continue;
}
}
}
$this->getProvider()->getLevel()->timings->syncChunkLoadEntitiesTimer->stopTiming();
$this->getProvider()->getLevel()->timings->syncChunkLoadTileEntitiesTimer->startTiming();
foreach($this->NBTtiles as $nbt){
if($nbt instanceof CompoundTag){
if(!isset($nbt->id)){
$changed = true;
continue;
}
if(($nbt["x"] >> 4) !== $this->x or ($nbt["z"] >> 4) !== $this->z){
$changed = true;
continue; //Fixes tiles allocated in wrong chunks.
}
if(Tile::createTile($nbt["id"], $this, $nbt) === null){
$changed = true;
continue;
}
}
}
$this->getProvider()->getLevel()->timings->syncChunkLoadTileEntitiesTimer->stopTiming();
$this->NBTentities = null;
$this->NBTtiles = null;
}
$this->hasChanged = $changed;
$this->isInit = true;
}
}
public function getBiomeIdArray() : string{
return $this->biomeIds;
}
public function getHeightMapArray() : array{
return $this->heightMap;
}
public function getBlockExtraDataArray() : array{
return $this->extraData;
}
public function hasChanged() : bool{
return $this->hasChanged;
}
public function setChanged(bool $value = true){
$this->hasChanged = $value;
}
public function getSubChunk(int $y, bool $generateNew = false) : SubChunk{
if($y < 0 or $y >= $this->height){
return $this->emptySubChunk;
}elseif($generateNew and $this->subChunks[$y] instanceof EmptySubChunk){
$this->subChunks[$y] = new SubChunk();
}
assert($this->subChunks[$y] !== null, "Somehow something broke, no such subchunk at index $y");
return $this->subChunks[$y];
}
public function setSubChunk(int $y, SubChunk $subChunk = null, bool $allowEmpty = false) : bool{
if($y < 0 or $y >= $this->height){
return false;
}
if($subChunk === null or ($subChunk->isEmpty() and !$allowEmpty)){
$this->subChunks[$y] = $this->emptySubChunk;
}else{
$this->subChunks[$y] = $subChunk;
}
$this->hasChanged = true;
return true;
}
public function getSubChunks() : array{
return $this->subChunks;
}
public function getHighestSubChunkIndex() : int{
for($y = count($this->subChunks) - 1; $y >= 0; --$y){
if($this->subChunks[$y] === null or $this->subChunks[$y] instanceof EmptySubChunk){
//No need to thoroughly prune empties at runtime, this will just reduce performance.
continue;
}
break;
}
return $y;
}
public function getSubChunkSendCount() : int{
return $this->getHighestSubChunkIndex() + 1;
}
public function pruneEmptySubChunks(){
foreach($this->subChunks as $y => $subChunk){
if($y < 0 or $y >= $this->height){
assert(false, "Invalid subchunk index");
unset($this->subChunks[$y]);
}elseif($subChunk instanceof EmptySubChunk){
continue;
}elseif($subChunk->isEmpty()){ //normal subchunk full of air, remove it and replace it with an empty stub
$this->subChunks[$y] = $this->emptySubChunk;
}else{
continue; //do not set changed
}
$this->hasChanged = true;
}
}
public function networkSerialize() : string{
$result = "";
$subChunkCount = $this->getSubChunkSendCount();
$result .= chr($subChunkCount);
for($y = 0; $y < $subChunkCount; ++$y){
$result .= $this->subChunks[$y]->networkSerialize();
}
$result .= pack("v*", ...$this->heightMap)
. $this->biomeIds
. chr(0); //border block array count
//Border block entry format: 1 byte (4 bits X, 4 bits Z). These are however useless since they crash the regular client.
$extraData = new BinaryStream();
$extraData->putVarInt(count($this->extraData)); //WHY, Mojang, WHY
foreach($this->extraData as $key => $value){
$extraData->putVarInt($key);
$extraData->putLShort($value);
}
$result .= $extraData->getBuffer();
if(count($this->tiles) > 0){
$nbt = new NBT(NBT::LITTLE_ENDIAN);
$list = [];
foreach($this->tiles as $tile){
if($tile instanceof Spawnable){
$list[] = $tile->getSpawnCompound();
}
}
$nbt->setData($list);
$result .= $nbt->write(true);
}
return $result;
}
public function fastSerialize() : string{
$stream = new BinaryStream();
$stream->putInt($this->x);
$stream->putInt($this->z);
$count = 0;
$subChunks = "";
foreach($this->subChunks as $y => $subChunk){
if($subChunk instanceof EmptySubChunk){
continue;
}
++$count;
$subChunks .= chr($y) . $subChunk->fastSerialize();
}
$stream->putByte($count);
$stream->put($subChunks);
$stream->put(pack("C*", ...$this->heightMap) .
$this->biomeIds .
chr(($this->lightPopulated ? 1 << 2 : 0) | ($this->terrainPopulated ? 1 << 1 : 0) | ($this->terrainGenerated ? 1 : 0)));
//TODO: tiles and entities
return $stream->getBuffer();
}
public static function fastDeserialize(string $data, LevelProvider $provider = null){
$stream = new BinaryStream();
$stream->setBuffer($data);
$data = null;
$x = $stream->getInt();
$z = $stream->getInt();
$subChunks = [];
$count = $stream->getByte();
for($y = 0; $y < $count; ++$y){
$subChunks[$stream->getByte()] = SubChunk::fastDeserialize($stream->get(10240));
}
$heightMap = array_values(unpack("C*", $stream->get(256)));
$biomeIds = $stream->get(256);
$chunk = new GenericChunk($provider, $x, $z, $subChunks, [], [], $biomeIds, $heightMap);
$flags = $stream->getByte();
$chunk->lightPopulated = (bool) ($flags & 4);
$chunk->terrainPopulated = (bool) ($flags & 2);
$chunk->terrainGenerated = (bool) ($flags & 1);
return $chunk;
}
//TODO: get rid of this
public static function getEmptyChunk(int $x, int $z, LevelProvider $provider = null) : Chunk{
return new GenericChunk($provider, $x, $z);
}
/**
* Creates a block hash from chunk block coordinates. Used for extra data keys in chunk packets.
* @internal
*
* @param int $x 0-15
* @param int $y 0-255
* @param int $z 0-15
*
* @return int
*/
public static function chunkBlockHash(int $x, int $y, int $z) : int{
return ($x << 12) | ($z << 8) | $y;
}
/**
* Re-orders a byte array (YZX -> XZY and vice versa)
*
* @param string $array length 4096
*
* @return string length 4096
*/
public static final function reorderByteArray(string $array) : string{
$result = str_repeat("\x00", 4096);
$i = 0;
for($x = 0; $x < 16; ++$x){
for($z = 0; $z < 256; $z += 16){
$zx = ($z + $x);
for($y = 0; $y < 4096; $y += 256){
$result{$i} = $array{$y + $zx};
++$i;
}
}
}
return $result;
}
/**
* Re-orders a nibble array (YZX -> XZY and vice versa)
*
* @param string $array length 2048
*
* @return string length 2048
*/
public static final function reorderNibbleArray(string $array) : string{
$result = str_repeat("\x00", 2048);
$i = 0;
for($x = 0; $x < 8; ++$x){
for($z = 0; $z < 16; ++$z){
$zx = (($z << 3) | $x);
for($y = 0; $y < 8; ++$y){
$j = (($y << 8) | $zx);
$i1 = ord($array{$j});
$i2 = ord($array{$j | 0x80});
$result{$i} = chr(($i2 << 4) | ($i1 & 0x0f));
$result{$i | 0x80} = chr(($i1 >> 4) | ($i2 & 0xf0));
$i++;
}
}
$i += 128;
}
return $result;
}
/**
* Converts pre-MCPE-1.0 biome colour array to biome ID array. RIP BiomeColors :(
*
* @param int[] $array of biome colour values
*
* @return string
*/
public static function convertBiomeColours(array $array) : string{
$result = str_repeat("\x00", 256);
foreach($array as $i => $colour){
$result{$i} = chr(($array[$i] >> 24) & 0xff);
}
return $result;
}
}

View File

@ -21,9 +21,9 @@
declare(strict_types = 1);
namespace pocketmine\level\format\generic;
namespace pocketmine\level\format\io;
use pocketmine\level\format\LevelProvider;
use pocketmine\level\format\Chunk;
use pocketmine\level\generator\Generator;
use pocketmine\level\Level;
use pocketmine\math\Vector3;
@ -42,6 +42,8 @@ abstract class BaseLevelProvider implements LevelProvider{
protected $path;
/** @var CompoundTag */
protected $levelData;
/** @var bool */
protected $asyncChunkRequest = false;
public function __construct(Level $level, string $path){
$this->level = $level;
@ -65,6 +67,7 @@ abstract class BaseLevelProvider implements LevelProvider{
if(!isset($this->levelData->generatorOptions)){
$this->levelData->generatorOptions = new StringTag("generatorOptions", "");
}
$this->asyncChunkRequest = (bool) $this->level->getServer()->getProperty("chunk-sending.async-chunk-request", false);
}
public function getPath() : string{
@ -131,10 +134,17 @@ abstract class BaseLevelProvider implements LevelProvider{
public function requestChunkTask(int $x, int $z){
$chunk = $this->getChunk($x, $z, false);
if(!($chunk instanceof GenericChunk)){
if(!($chunk instanceof Chunk)){
throw new ChunkException("Invalid Chunk sent");
}
if($this->asyncChunkRequest){
return new ChunkRequestTask($this->level, $chunk);
}
//non-async, call the callback directly with serialized data
$this->getLevel()->chunkRequestCallback($x, $z, $chunk->networkSerialize());
return null;
}
}

View File

@ -19,7 +19,7 @@
*
*/
namespace pocketmine\level\format\generic;
namespace pocketmine\level\format\io;
use pocketmine\level\format\Chunk;
use pocketmine\level\Level;
@ -59,7 +59,7 @@ class ChunkRequestTask extends AsyncTask{
}
public function onRun(){
$chunk = GenericChunk::fastDeserialize($this->chunk);
$chunk = Chunk::fastDeserialize($this->chunk);
$ordered = $chunk->networkSerialize();

View File

@ -21,8 +21,9 @@
declare(strict_types = 1);
namespace pocketmine\level\format;
namespace pocketmine\level\format\io;
use pocketmine\level\format\Chunk;
use pocketmine\level\Level;
use pocketmine\math\Vector3;

View File

@ -21,7 +21,7 @@
declare(strict_types = 1);
namespace pocketmine\level\format;
namespace pocketmine\level\format\io;
use pocketmine\utils\LevelException;

View File

@ -21,19 +21,13 @@
declare(strict_types = 1);
namespace pocketmine\level\format\region;
namespace pocketmine\level\format\io\region;
use pocketmine\level\format\generic\GenericChunk;
use pocketmine\level\format\generic\SubChunk;
use pocketmine\level\format\Chunk;
use pocketmine\level\format\SubChunk;
use pocketmine\nbt\NBT;
use pocketmine\nbt\tag\{
ByteArrayTag,
ByteTag,
CompoundTag,
IntArrayTag,
IntTag,
ListTag,
LongTag
ByteArrayTag, ByteTag, CompoundTag, IntArrayTag, IntTag, ListTag, LongTag
};
use pocketmine\Player;
use pocketmine\utils\ChunkException;
@ -43,7 +37,7 @@ class Anvil extends McRegion{
const REGION_FILE_EXTENSION = "mca";
public function nbtSerialize(GenericChunk $chunk) : string{
public function nbtSerialize(Chunk $chunk) : string{
$nbt = new CompoundTag("Level", []);
$nbt->xPos = new IntTag("xPos", $chunk->getX());
$nbt->zPos = new IntTag("zPos", $chunk->getZ());
@ -63,10 +57,10 @@ class Anvil extends McRegion{
}
$nbt->Sections[++$subChunks] = new CompoundTag(null, [
"Y" => new ByteTag("Y", $y),
"Blocks" => new ByteArrayTag("Blocks", GenericChunk::reorderByteArray($subChunk->getBlockIdArray())), //Generic in-memory chunks are currently always XZY
"Data" => new ByteArrayTag("Data", GenericChunk::reorderNibbleArray($subChunk->getBlockDataArray())),
"SkyLight" => new ByteArrayTag("SkyLight", GenericChunk::reorderNibbleArray($subChunk->getSkyLightArray())),
"BlockLight" => new ByteArrayTag("BlockLight", GenericChunk::reorderNibbleArray($subChunk->getBlockLightArray()))
"Blocks" => new ByteArrayTag("Blocks", Chunk::reorderByteArray($subChunk->getBlockIdArray())), //Generic in-memory chunks are currently always XZY
"Data" => new ByteArrayTag("Data", Chunk::reorderNibbleArray($subChunk->getBlockDataArray())),
"SkyLight" => new ByteArrayTag("SkyLight", Chunk::reorderNibbleArray($subChunk->getSkyLightArray())),
"BlockLight" => new ByteArrayTag("BlockLight", Chunk::reorderNibbleArray($subChunk->getBlockLightArray()))
]);
}
@ -121,24 +115,24 @@ class Anvil extends McRegion{
foreach($chunk->Sections as $subChunk){
if($subChunk instanceof CompoundTag){
$subChunks[$subChunk->Y->getValue()] = new SubChunk(
GenericChunk::reorderByteArray($subChunk->Blocks->getValue()),
GenericChunk::reorderNibbleArray($subChunk->Data->getValue()),
GenericChunk::reorderNibbleArray($subChunk->SkyLight->getValue()),
GenericChunk::reorderNibbleArray($subChunk->BlockLight->getValue())
Chunk::reorderByteArray($subChunk->Blocks->getValue()),
Chunk::reorderNibbleArray($subChunk->Data->getValue()),
Chunk::reorderNibbleArray($subChunk->SkyLight->getValue()),
Chunk::reorderNibbleArray($subChunk->BlockLight->getValue())
);
}
}
}
if(isset($chunk->BiomeColors)){
$biomeIds = GenericChunk::convertBiomeColours($chunk->BiomeColors->getValue()); //Convert back to PC format (RIP colours D:)
$biomeIds = Chunk::convertBiomeColours($chunk->BiomeColors->getValue()); //Convert back to PC format (RIP colours D:)
}elseif(isset($chunk->Biomes)){
$biomeIds = $chunk->Biomes->getValue();
}else{
$biomeIds = "";
}
$result = new GenericChunk(
$result = new Chunk(
$this,
$chunk["xPos"],
$chunk["zPos"],

View File

@ -21,24 +21,16 @@
declare(strict_types = 1);
namespace pocketmine\level\format\region;
namespace pocketmine\level\format\io\region;
use pocketmine\level\format\Chunk;
use pocketmine\level\format\generic\GenericChunk;
use pocketmine\level\format\generic\SubChunk;
use pocketmine\level\format\generic\BaseLevelProvider;
use pocketmine\level\format\io\BaseLevelProvider;
use pocketmine\level\format\SubChunk;
use pocketmine\level\generator\Generator;
use pocketmine\level\Level;
use pocketmine\nbt\NBT;
use pocketmine\nbt\tag\{
ByteArrayTag,
ByteTag,
CompoundTag,
IntArrayTag,
IntTag,
ListTag,
LongTag,
StringTag
ByteArrayTag, ByteTag, CompoundTag, IntArrayTag, IntTag, ListTag, LongTag, StringTag
};
use pocketmine\Player;
use pocketmine\utils\MainLogger;
@ -50,15 +42,15 @@ class McRegion extends BaseLevelProvider{
/** @var RegionLoader[] */
protected $regions = [];
/** @var GenericChunk[] */
/** @var Chunk[] */
protected $chunks = [];
/**
* @param GenericChunk $chunk
* @param Chunk $chunk
*
* @return string
*/
public function nbtSerialize(GenericChunk $chunk) : string{
public function nbtSerialize(Chunk $chunk) : string{
$nbt = new CompoundTag("Level", []);
$nbt->xPos = new IntTag("xPos", $chunk->getX());
$nbt->zPos = new IntTag("zPos", $chunk->getZ());
@ -127,7 +119,7 @@ class McRegion extends BaseLevelProvider{
/**
* @param string $data
*
* @return GenericChunk|null
* @return Chunk|null
*/
public function nbtDeserialize(string $data){
$nbt = new NBT(NBT::BIG_ENDIAN);
@ -177,14 +169,14 @@ class McRegion extends BaseLevelProvider{
}
if(isset($chunk->BiomeColors)){
$biomeIds = GenericChunk::convertBiomeColours($chunk->BiomeColors->getValue()); //Convert back to PC format (RIP colours D:)
$biomeIds = Chunk::convertBiomeColours($chunk->BiomeColors->getValue()); //Convert back to PC format (RIP colours D:)
}elseif(isset($chunk->Biomes)){
$biomeIds = $chunk->Biomes->getValue();
}else{
$biomeIds = "";
}
$result = new GenericChunk(
$result = new Chunk(
$this,
$chunk["xPos"],
$chunk["zPos"],
@ -414,10 +406,10 @@ class McRegion extends BaseLevelProvider{
* @param int $chunkX
* @param int $chunkZ
*
* @return GenericChunk
* @return Chunk
*/
public function getEmptyChunk(int $chunkX, int $chunkZ){
return GenericChunk::getEmptyChunk($chunkX, $chunkZ, $this);
return Chunk::getEmptyChunk($chunkX, $chunkZ, $this);
}
/**

View File

@ -21,19 +21,13 @@
declare(strict_types = 1);
namespace pocketmine\level\format\region;
namespace pocketmine\level\format\io\region;
use pocketmine\level\format\generic\GenericChunk;
use pocketmine\level\format\generic\SubChunk;
use pocketmine\level\format\Chunk;
use pocketmine\level\format\SubChunk;
use pocketmine\nbt\NBT;
use pocketmine\nbt\tag\{
ByteArrayTag,
ByteTag,
CompoundTag,
IntArrayTag,
IntTag,
ListTag,
LongTag
ByteArrayTag, ByteTag, CompoundTag, IntArrayTag, IntTag, ListTag, LongTag
};
use pocketmine\Player;
use pocketmine\utils\ChunkException;
@ -47,7 +41,7 @@ class PMAnvil extends Anvil{
const REGION_FILE_EXTENSION = "mcapm";
public function nbtSerialize(GenericChunk $chunk) : string{
public function nbtSerialize(Chunk $chunk) : string{
$nbt = new CompoundTag("Level", []);
$nbt->xPos = new IntTag("xPos", $chunk->getX());
$nbt->zPos = new IntTag("zPos", $chunk->getZ());
@ -134,7 +128,7 @@ class PMAnvil extends Anvil{
}
}
$result = new GenericChunk(
$result = new Chunk(
$this,
$chunk["xPos"],
$chunk["zPos"],

View File

@ -21,10 +21,10 @@
declare(strict_types = 1);
namespace pocketmine\level\format\region;
namespace pocketmine\level\format\io\region;
use pocketmine\level\format\generic\GenericChunk;
use pocketmine\level\format\LevelProvider;
use pocketmine\level\format\Chunk;
use pocketmine\level\format\io\LevelProvider;
use pocketmine\utils\Binary;
use pocketmine\utils\ChunkException;
use pocketmine\utils\MainLogger;
@ -114,7 +114,7 @@ class RegionLoader{
}
$chunk = $this->levelProvider->nbtDeserialize(fread($this->filePointer, $length - 1));
if($chunk instanceof GenericChunk){
if($chunk instanceof Chunk){
return $chunk;
}else{
MainLogger::getLogger()->error("Corrupted chunk detected");
@ -159,7 +159,7 @@ class RegionLoader{
$this->locationTable[$index][1] = 0;
}
public function writeChunk(GenericChunk $chunk){
public function writeChunk(Chunk $chunk){
$this->lastUsed = time();
$chunkData = $this->levelProvider->nbtSerialize($chunk);
if($chunkData !== false){

View File

@ -1,394 +0,0 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
namespace pocketmine\level\format\leveldb;
use pocketmine\level\format\generic\BaseChunk;
use pocketmine\level\format\LevelProvider;
use pocketmine\nbt\NBT;
use pocketmine\Player;
use pocketmine\utils\Binary;
use pocketmine\utils\BinaryStream;
class Chunk extends BaseChunk{
const DATA_LENGTH = 16384 * (2 + 1 + 1 + 1) + 256 + 1024;
protected $isLightPopulated = false;
protected $isPopulated = false;
protected $isGenerated = false;
public function __construct($level, $chunkX, $chunkZ, $terrain, array $entityData = null, array $tileData = null){
$offset = 0;
$blocks = substr($terrain, $offset, 32768);
$offset += 32768;
$data = substr($terrain, $offset, 16384);
$offset += 16384;
$skyLight = substr($terrain, $offset, 16384);
$offset += 16384;
$blockLight = substr($terrain, $offset, 16384);
$offset += 16384;
$heightMap = [];
foreach(unpack("C*", substr($terrain, $offset, 256)) as $c){
$heightMap[] = $c;
}
$offset += 256;
$biomeColors = [];
foreach(unpack("N*", substr($terrain, $offset, 1024)) as $c){
$biomeColors[] = $c;
}
$offset += 1024;
parent::__construct($level, $chunkX, $chunkZ, $blocks, $data, $skyLight, $blockLight, $biomeColors, $heightMap, $entityData === null ? [] : $entityData, $tileData === null ? [] : $tileData);
}
public function getBlockId($x, $y, $z){
return ord($this->blocks{($x << 11) | ($z << 7) | $y});
}
public function setBlockId($x, $y, $z, $id){
$this->blocks{($x << 11) | ($z << 7) | $y} = chr($id);
$this->hasChanged = true;
}
public function getBlockData($x, $y, $z){
$m = ord($this->data{($x << 10) | ($z << 6) | ($y >> 1)});
if(($y & 1) === 0){
return $m & 0x0F;
}else{
return $m >> 4;
}
}
public function setBlockData($x, $y, $z, $data){
$i = ($x << 10) | ($z << 6) | ($y >> 1);
$old_m = ord($this->data{$i});
if(($y & 1) === 0){
$this->data{$i} = chr(($old_m & 0xf0) | ($data & 0x0f));
}else{
$this->data{$i} = chr((($data & 0x0f) << 4) | ($old_m & 0x0f));
}
$this->hasChanged = true;
}
public function getFullBlock($x, $y, $z){
$i = ($x << 11) | ($z << 7) | $y;
if(($y & 1) === 0){
return (ord($this->blocks{$i}) << 4) | (ord($this->data{$i >> 1}) & 0x0F);
}else{
return (ord($this->blocks{$i}) << 4) | (ord($this->data{$i >> 1}) >> 4);
}
}
public function setBlock($x, $y, $z, $blockId = null, $meta = null){
$i = ($x << 11) | ($z << 7) | $y;
$changed = false;
if($blockId !== null){
$blockId = chr($blockId);
if($this->blocks{$i} !== $blockId){
$this->blocks{$i} = $blockId;
$changed = true;
}
}
if($meta !== null){
$i >>= 1;
$old_m = ord($this->data{$i});
if(($y & 1) === 0){
$this->data{$i} = chr(($old_m & 0xf0) | ($meta & 0x0f));
if(($old_m & 0x0f) !== $meta){
$changed = true;
}
}else{
$this->data{$i} = chr((($meta & 0x0f) << 4) | ($old_m & 0x0f));
if((($old_m & 0xf0) >> 4) !== $meta){
$changed = true;
}
}
}
if($changed){
$this->hasChanged = true;
}
return $changed;
}
public function getBlockSkyLight($x, $y, $z){
$sl = ord($this->skyLight{($x << 10) | ($z << 6) | ($y >> 1)});
if(($y & 1) === 0){
return $sl & 0x0F;
}else{
return $sl >> 4;
}
}
public function setBlockSkyLight($x, $y, $z, $level){
$i = ($x << 10) | ($z << 6) | ($y >> 1);
$old_sl = ord($this->skyLight{$i});
if(($y & 1) === 0){
$this->skyLight{$i} = chr(($old_sl & 0xf0) | ($level & 0x0f));
}else{
$this->skyLight{$i} = chr((($level & 0x0f) << 4) | ($old_sl & 0x0f));
}
$this->hasChanged = true;
}
public function getBlockLight($x, $y, $z){
$l = ord($this->blockLight{($x << 10) | ($z << 6) | ($y >> 1)});
if(($y & 1) === 0){
return $l & 0x0F;
}else{
return $l >> 4;
}
}
public function setBlockLight($x, $y, $z, $level){
$i = ($x << 10) | ($z << 6) | ($y >> 1);
$old_l = ord($this->blockLight{$i});
if(($y & 1) === 0){
$this->blockLight{$i} = chr(($old_l & 0xf0) | ($level & 0x0f));
}else{
$this->blockLight{$i} = chr((($level & 0x0f) << 4) | ($old_l & 0x0f));
}
$this->hasChanged = true;
}
public function getBlockIdColumn($x, $z){
return substr($this->blocks, ($x << 11) + ($z << 7), 128);
}
public function getBlockDataColumn($x, $z){
return substr($this->data, ($x << 10) + ($z << 6), 64);
}
public function getBlockSkyLightColumn($x, $z){
return substr($this->skyLight, ($x << 10) + ($z << 6), 64);
}
public function getBlockLightColumn($x, $z){
return substr($this->blockLight, ($x << 10) + ($z << 6), 64);
}
/**
* @return bool
*/
public function isLightPopulated(){
return $this->isLightPopulated;
}
/**
* @param int $value
*/
public function setLightPopulated($value = 1){
$this->isLightPopulated = (bool) $value;
}
/**
* @return bool
*/
public function isPopulated(){
return $this->isPopulated;
}
/**
* @param int $value
*/
public function setPopulated($value = 1){
$this->isPopulated = (bool) $value;
}
/**
* @return bool
*/
public function isGenerated(){
return $this->isGenerated;
}
/**
* @param int $value
*/
public function setGenerated($value = 1){
$this->isGenerated = (bool) $value;
}
public static function fromFastBinary($data, LevelProvider $provider = null){
return self::fromBinary($data, $provider);
}
/**
* @param string $data
* @param LevelProvider $provider
*
* @return Chunk
*/
public static function fromBinary($data, LevelProvider $provider = null){
try{
$chunkX = Binary::readLInt(substr($data, 0, 4));
$chunkZ = Binary::readLInt(substr($data, 4, 4));
$chunkData = substr($data, 8, -1);
$flags = ord(substr($data, -1));
$entities = null;
$tiles = null;
$extraData = [];
if($provider instanceof LevelDB){
$nbt = new NBT(NBT::LITTLE_ENDIAN);
$entityData = $provider->getDatabase()->get(substr($data, 0, 8) . LevelDB::ENTRY_ENTITIES);
if($entityData !== false and strlen($entityData) > 0){
$nbt->read($entityData, true);
$entities = $nbt->getData();
if(!is_array($entities)){
$entities = [$entities];
}
}
$tileData = $provider->getDatabase()->get(substr($data, 0, 8) . LevelDB::ENTRY_TILES);
if($tileData !== false and strlen($tileData) > 0){
$nbt->read($tileData, true);
$tiles = $nbt->getData();
if(!is_array($tiles)){
$tiles = [$tiles];
}
}
$tileData = $provider->getDatabase()->get(substr($data, 0, 8) . LevelDB::ENTRY_EXTRA_DATA);
if($tileData !== false and strlen($tileData) > 0){
$stream = new BinaryStream($tileData);
$count = $stream->getInt();
for($i = 0; $i < $count; ++$i){
$key = $stream->getInt();
$value = $stream->getShort(false);
$extraData[$key] = $value;
}
}
}
$chunk = new Chunk($provider instanceof LevelProvider ? $provider : LevelDB::class, $chunkX, $chunkZ, $chunkData, $entities, $tiles);
if($flags & 0x01){
$chunk->setGenerated();
}
if($flags & 0x02){
$chunk->setPopulated();
}
if($flags & 0x04){
$chunk->setLightPopulated();
}
return $chunk;
}catch(\Throwable $e){
return null;
}
}
public function toFastBinary(){
return $this->toBinary(false);
}
public function toBinary($saveExtra = false){
$chunkIndex = LevelDB::chunkIndex($this->getX(), $this->getZ());
$provider = $this->getProvider();
if($saveExtra and $provider instanceof LevelDB){
$nbt = new NBT(NBT::LITTLE_ENDIAN);
$entities = [];
foreach($this->getEntities() as $entity){
if(!($entity instanceof Player) and !$entity->closed){
$entity->saveNBT();
$entities[] = $entity->namedtag;
}
}
if(count($entities) > 0){
$nbt->setData($entities);
$provider->getDatabase()->put($chunkIndex . LevelDB::ENTRY_ENTITIES, $nbt->write());
}else{
$provider->getDatabase()->delete($chunkIndex . LevelDB::ENTRY_ENTITIES);
}
$tiles = [];
foreach($this->getTiles() as $tile){
if(!$tile->closed){
$tile->saveNBT();
$tiles[] = $tile->namedtag;
}
}
if(count($tiles) > 0){
$nbt->setData($tiles);
$provider->getDatabase()->put($chunkIndex . LevelDB::ENTRY_TILES, $nbt->write());
}else{
$provider->getDatabase()->delete($chunkIndex . LevelDB::ENTRY_TILES);
}
if(count($this->getBlockExtraDataArray()) > 0){
$extraData = new BinaryStream();
$extraData->putInt(count($this->getBlockExtraDataArray()));
foreach($this->getBlockExtraDataArray() as $key => $value){
$extraData->putInt($key);
$extraData->putShort($value);
}
$provider->getDatabase()->put($chunkIndex . LevelDB::ENTRY_EXTRA_DATA, $extraData->getBuffer());
}else{
$provider->getDatabase()->delete($chunkIndex . LevelDB::ENTRY_EXTRA_DATA);
}
}
$heightmap = pack("C*", ...$this->getHeightMapArray());
$biomeColors = pack("N*", ...$this->getBiomeColorArray());
return $chunkIndex .
$this->getBlockIdArray() .
$this->getBlockDataArray() .
$this->getBlockSkyLightArray() .
$this->getBlockLightArray() .
$heightmap .
$biomeColors . chr(
($this->isLightPopulated() ? 0x04 : 0) | ($this->isPopulated() ? 0x02 : 0) | ($this->isGenerated() ? 0x01 : 0)
);
}
/**
* @param int $chunkX
* @param int $chunkZ
* @param LevelProvider $provider
*
* @return Chunk
*/
public static function getEmptyChunk($chunkX, $chunkZ, LevelProvider $provider = null){
try{
$chunk = new Chunk($provider instanceof LevelProvider ? $provider : LevelDB::class, $chunkX, $chunkZ, str_repeat("\x00", self::DATA_LENGTH));
$chunk->skyLight = str_repeat("\xff", 16384);
return $chunk;
}catch(\Throwable $e){
return null;
}
}
}

View File

@ -1,356 +0,0 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
namespace pocketmine\level\format\leveldb;
use pocketmine\level\format\Chunk;
use pocketmine\level\format\generic\BaseLevelProvider;
use pocketmine\level\generator\Generator;
use pocketmine\level\Level;
use pocketmine\nbt\NBT;
use pocketmine\nbt\tag\ByteTag;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\IntTag;
use pocketmine\nbt\tag\LongTag;
use pocketmine\nbt\tag\StringTag;
use pocketmine\tile\Spawnable;
use pocketmine\utils\Binary;
use pocketmine\utils\BinaryStream;
use pocketmine\utils\ChunkException;
use pocketmine\utils\LevelException;
class LevelDB extends BaseLevelProvider{
const ENTRY_VERSION = "v";
const ENTRY_FLAGS = "f";
const ENTRY_EXTRA_DATA = "4";
const ENTRY_TICKS = "3";
const ENTRY_ENTITIES = "2";
const ENTRY_TILES = "1";
const ENTRY_TERRAIN = "0";
/** @var Chunk[] */
protected $chunks = [];
/** @var \LevelDB */
protected $db;
public function __construct(Level $level, $path){
$this->level = $level;
$this->path = $path;
if(!file_exists($this->path)){
mkdir($this->path, 0777, true);
}
$nbt = new NBT(NBT::LITTLE_ENDIAN);
$nbt->read(substr(file_get_contents($this->getPath() . "level.dat"), 8));
$levelData = $nbt->getData();
if($levelData instanceof CompoundTag){
$this->levelData = $levelData;
}else{
throw new LevelException("Invalid level.dat");
}
if(!isset($this->levelData->generatorName)){
$this->levelData->generatorName = new StringTag("generatorName", Generator::getGenerator("DEFAULT"));
}
if(!isset($this->levelData->generatorOptions)){
$this->levelData->generatorOptions = new StringTag("generatorOptions", "");
}
$this->db = new \LevelDB($this->path . "/db", [
"compression" => LEVELDB_ZLIB_COMPRESSION
]);
}
public static function getProviderName(){
return "leveldb";
}
public static function getProviderOrder(){
return self::ORDER_ZXY;
}
public static function usesChunkSection(){
return false;
}
public static function isValid($path){
return file_exists($path . "/level.dat") and is_dir($path . "/db/");
}
public static function generate($path, $name, $seed, $generator, array $options = []){
if(!file_exists($path)){
mkdir($path, 0777, true);
}
if(!file_exists($path . "/db")){
mkdir($path . "/db", 0777, true);
}
//TODO, add extra details
$levelData = new CompoundTag("", [
"hardcore" => new ByteTag("hardcore", 0),
"initialized" => new ByteTag("initialized", 1),
"GameType" => new IntTag("GameType", 0),
"generatorVersion" => new IntTag("generatorVersion", 1), //2 in MCPE
"SpawnX" => new IntTag("SpawnX", 128),
"SpawnY" => new IntTag("SpawnY", 70),
"SpawnZ" => new IntTag("SpawnZ", 128),
"version" => new IntTag("version", 19133),
"DayTime" => new IntTag("DayTime", 0),
"LastPlayed" => new LongTag("LastPlayed", microtime(true) * 1000),
"RandomSeed" => new LongTag("RandomSeed", $seed),
"SizeOnDisk" => new LongTag("SizeOnDisk", 0),
"Time" => new LongTag("Time", 0),
"generatorName" => new StringTag("generatorName", Generator::getGeneratorName($generator)),
"generatorOptions" => new StringTag("generatorOptions", isset($options["preset"]) ? $options["preset"] : ""),
"LevelName" => new StringTag("LevelName", $name),
"GameRules" => new CompoundTag("GameRules", [])
]);
$nbt = new NBT(NBT::LITTLE_ENDIAN);
$nbt->setData($levelData);
$buffer = $nbt->write();
file_put_contents($path . "level.dat", Binary::writeLInt(3) . Binary::writeLInt(strlen($buffer)) . $buffer);
$db = new \LevelDB($path . "/db");
$db->close();
}
public function saveLevelData(){
$nbt = new NBT(NBT::LITTLE_ENDIAN);
$nbt->setData($this->levelData);
$buffer = $nbt->write();
file_put_contents($this->getPath() . "level.dat", Binary::writeLInt(3) . Binary::writeLInt(strlen($buffer)) . $buffer);
}
public function requestChunkTask($x, $z){
$chunk = $this->getChunk($x, $z, false);
if(!($chunk instanceof Chunk)){
throw new ChunkException("Invalid Chunk sent");
}
$tiles = "";
$nbt = new NBT(NBT::LITTLE_ENDIAN);
foreach($chunk->getTiles() as $tile){
if($tile instanceof Spawnable){
$nbt->setData($tile->getSpawnCompound());
$tiles .= $nbt->write();
}
}
$heightmap = pack("C*", ...$chunk->getHeightMapArray());
$biomeColors = pack("N*", ...$chunk->getBiomeColorArray());
$extraData = new BinaryStream();
$extraData->putLInt(count($chunk->getBlockExtraDataArray()));
foreach($chunk->getBlockExtraDataArray() as $key => $value){
$extraData->putLInt($key);
$extraData->putLShort($value);
}
$ordered = $chunk->getBlockIdArray() .
$chunk->getBlockDataArray() .
$chunk->getBlockSkyLightArray() .
$chunk->getBlockLightArray() .
$heightmap .
$biomeColors .
$extraData->getBuffer() .
$tiles;
$this->getLevel()->chunkRequestCallback($x, $z, $ordered);
return null;
}
public function unloadChunks(){
foreach($this->chunks as $chunk){
$this->unloadChunk($chunk->getX(), $chunk->getZ(), false);
}
$this->chunks = [];
}
public function getGenerator(){
return $this->levelData["generatorName"];
}
public function getGeneratorOptions(){
return ["preset" => $this->levelData["generatorOptions"]];
}
public function getLoadedChunks(){
return $this->chunks;
}
public function isChunkLoaded($x, $z){
return isset($this->chunks[Level::chunkHash($x, $z)]);
}
public function saveChunks(){
foreach($this->chunks as $chunk){
$this->saveChunk($chunk->getX(), $chunk->getZ());
}
}
public function loadChunk($chunkX, $chunkZ, $create = false){
if(isset($this->chunks[$index = Level::chunkHash($chunkX, $chunkZ)])){
return true;
}
$this->level->timings->syncChunkLoadDataTimer->startTiming();
$chunk = $this->readChunk($chunkX, $chunkZ, $create);
if($chunk === null and $create){
$chunk = Chunk::getEmptyChunk($chunkX, $chunkZ, $this);
}
$this->level->timings->syncChunkLoadDataTimer->stopTiming();
if($chunk !== null){
$this->chunks[$index] = $chunk;
return true;
}else{
return false;
}
}
/**
* @param $chunkX
* @param $chunkZ
*
* @return Chunk
*/
private function readChunk($chunkX, $chunkZ){
$index = LevelDB::chunkIndex($chunkX, $chunkZ);
if(!$this->chunkExists($chunkX, $chunkZ) or ($data = $this->db->get($index . self::ENTRY_TERRAIN)) === false){
return null;
}
$flags = $this->db->get($index . self::ENTRY_FLAGS);
if($flags === false){
$flags = "\x03";
}
return Chunk::fromBinary($index . $data . $flags, $this);
}
private function writeChunk(Chunk $chunk){
$binary = $chunk->toBinary(true);
$index = LevelDB::chunkIndex($chunk->getX(), $chunk->getZ());
$this->db->put($index . self::ENTRY_TERRAIN, substr($binary, 8, -1));
$this->db->put($index . self::ENTRY_FLAGS, substr($binary, -1));
$this->db->put($index . self::ENTRY_VERSION, "\x02");
}
public function unloadChunk($x, $z, $safe = true){
$chunk = isset($this->chunks[$index = Level::chunkHash($x, $z)]) ? $this->chunks[$index] : null;
if($chunk instanceof Chunk and $chunk->unload(false, $safe)){
unset($this->chunks[$index]);
return true;
}
return false;
}
public function saveChunk($x, $z){
if($this->isChunkLoaded($x, $z)){
$this->writeChunk($this->getChunk($x, $z));
return true;
}
return false;
}
/**
* @param int $chunkX
* @param int $chunkZ
* @param bool $create
*
* @return Chunk
*/
public function getChunk($chunkX, $chunkZ, $create = false){
$index = Level::chunkHash($chunkX, $chunkZ);
if(isset($this->chunks[$index])){
return $this->chunks[$index];
}else{
$this->loadChunk($chunkX, $chunkZ, $create);
return isset($this->chunks[$index]) ? $this->chunks[$index] : null;
}
}
/**
* @return \LevelDB
*/
public function getDatabase(){
return $this->db;
}
public function setChunk($chunkX, $chunkZ, Chunk $chunk){
if(!($chunk instanceof Chunk)){
throw new ChunkException("Invalid Chunk class");
}
$chunk->setProvider($this);
$chunk->setX($chunkX);
$chunk->setZ($chunkZ);
if(isset($this->chunks[$index = Level::chunkHash($chunkX, $chunkZ)]) and $this->chunks[$index] !== $chunk){
$this->unloadChunk($chunkX, $chunkZ, false);
}
$this->chunks[$index] = $chunk;
}
public static function createChunkSection($Y){
return null;
}
public static function chunkIndex($chunkX, $chunkZ){
return Binary::writeLInt($chunkX) . Binary::writeLInt($chunkZ);
}
private function chunkExists($chunkX, $chunkZ){
return $this->db->get(LevelDB::chunkIndex($chunkX, $chunkZ) . self::ENTRY_VERSION) !== false;
}
public function isChunkGenerated($chunkX, $chunkZ){
if($this->chunkExists($chunkX, $chunkZ) and ($chunk = $this->getChunk($chunkX, $chunkZ, false)) !== null){
return true;
}
return false;
}
public function isChunkPopulated($chunkX, $chunkZ){
$chunk = $this->getChunk($chunkX, $chunkZ);
if($chunk instanceof Chunk){
return $chunk->isPopulated();
}else{
return false;
}
}
public function close(){
$this->unloadChunks();
$this->db->close();
$this->level = null;
}
}

View File

@ -22,7 +22,6 @@
namespace pocketmine\level\generator;
use pocketmine\level\format\Chunk;
use pocketmine\level\format\generic\GenericChunk;
use pocketmine\level\Level;
use pocketmine\level\SimpleChunkManager;
use pocketmine\scheduler\AsyncTask;
@ -51,7 +50,7 @@ class GenerationTask extends AsyncTask{
}
/** @var Chunk $chunk */
$chunk = GenericChunk::fastDeserialize($this->chunk);
$chunk = Chunk::fastDeserialize($this->chunk);
if($chunk === null){
//TODO error
return;
@ -76,7 +75,7 @@ class GenerationTask extends AsyncTask{
return;
}
/** @var Chunk $chunk */
$chunk = GenericChunk::fastDeserialize($this->chunk, $level->getProvider());
$chunk = Chunk::fastDeserialize($this->chunk, $level->getProvider());
if($chunk === null){
//TODO error
return;

View File

@ -22,7 +22,6 @@
namespace pocketmine\level\generator;
use pocketmine\level\format\Chunk;
use pocketmine\level\format\generic\GenericChunk;
use pocketmine\level\Level;
use pocketmine\scheduler\AsyncTask;
use pocketmine\Server;
@ -39,7 +38,7 @@ class LightPopulationTask extends AsyncTask{
public function onRun(){
/** @var Chunk $chunk */
$chunk = GenericChunk::fastDeserialize($this->chunk);
$chunk = Chunk::fastDeserialize($this->chunk);
if($chunk === null){
//TODO error
return;
@ -56,7 +55,7 @@ class LightPopulationTask extends AsyncTask{
$level = $server->getLevel($this->levelId);
if($level !== null){
/** @var Chunk $chunk */
$chunk = GenericChunk::fastDeserialize($this->chunk, $level->getProvider());
$chunk = Chunk::fastDeserialize($this->chunk, $level->getProvider());
if($chunk === null){
//TODO error
return;

View File

@ -22,7 +22,6 @@
namespace pocketmine\level\generator;
use pocketmine\level\format\Chunk;
use pocketmine\level\format\generic\GenericChunk;
use pocketmine\level\Level;
use pocketmine\level\SimpleChunkManager;
use pocketmine\scheduler\AsyncTask;
@ -73,7 +72,7 @@ class PopulationTask extends AsyncTask{
/** @var Chunk[] $chunks */
$chunks = [];
$chunk = GenericChunk::fastDeserialize($this->chunk);
$chunk = Chunk::fastDeserialize($this->chunk);
for($i = 0; $i < 9; ++$i){
if($i === 4){
@ -83,9 +82,9 @@ class PopulationTask extends AsyncTask{
$zz = -1 + (int) ($i / 3);
$ck = $this->{"chunk$i"};
if($ck === null){
$chunks[$i] = GenericChunk::getEmptyChunk($chunk->getX() + $xx, $chunk->getZ() + $zz);
$chunks[$i] = Chunk::getEmptyChunk($chunk->getX() + $xx, $chunk->getZ() + $zz);
}else{
$chunks[$i] = GenericChunk::fastDeserialize($ck);
$chunks[$i] = Chunk::fastDeserialize($ck);
}
}
@ -153,7 +152,7 @@ class PopulationTask extends AsyncTask{
return;
}
$chunk = GenericChunk::fastDeserialize($this->chunk, $level->getProvider());
$chunk = Chunk::fastDeserialize($this->chunk, $level->getProvider());
if($chunk === null){
//TODO error
@ -166,7 +165,7 @@ class PopulationTask extends AsyncTask{
}
$c = $this->{"chunk$i"};
if($c !== null){
$c = GenericChunk::fastDeserialize($c, $level->getProvider());
$c = Chunk::fastDeserialize($c, $level->getProvider());
$level->generateChunkCallback($c->getX(), $c->getZ(), $c);
}
}

View File

@ -53,8 +53,8 @@ use pocketmine\network\protocol\EntityEventPacket;
use pocketmine\network\protocol\ExplodePacket;
use pocketmine\network\protocol\FullChunkDataPacket;
use pocketmine\network\protocol\HurtArmorPacket;
use pocketmine\network\protocol\Info as ProtocolInfo;
use pocketmine\network\protocol\Info;
use pocketmine\network\protocol\Info as ProtocolInfo;
use pocketmine\network\protocol\InteractPacket;
use pocketmine\network\protocol\InventoryActionPacket;
use pocketmine\network\protocol\ItemFrameDropItemPacket;

View File

@ -115,6 +115,8 @@ chunk-sending:
#Save a serialized copy of the chunk in memory for faster sending
#Useful in mostly-static worlds where lots of players join at the same time
cache-chunks: false
#Use AsyncTasks for serializing chunks for sending.
async-chunk-request: false
chunk-ticking:
#Max amount of chunks processed each tick