mirror of
https://github.com/pmmp/PocketMine-MP.git
synced 2025-04-22 00:33:59 +00:00
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:
parent
d8908676ac
commit
ad0553fbf8
@ -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";
|
||||
|
||||
/*
|
||||
|
@ -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");
|
||||
|
@ -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;
|
||||
|
@ -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
@ -21,7 +21,7 @@
|
||||
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace pocketmine\level\format\generic;
|
||||
namespace pocketmine\level\format;
|
||||
|
||||
class EmptySubChunk extends SubChunk{
|
||||
|
@ -21,7 +21,7 @@
|
||||
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace pocketmine\level\format\generic;
|
||||
namespace pocketmine\level\format;
|
||||
|
||||
class SubChunk{
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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();
|
||||
|
@ -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;
|
||||
|
@ -21,7 +21,7 @@
|
||||
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace pocketmine\level\format;
|
||||
namespace pocketmine\level\format\io;
|
||||
|
||||
use pocketmine\utils\LevelException;
|
||||
|
@ -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"],
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
@ -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"],
|
@ -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){
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user