From 387677e9579999a33a630719bccb4aca2fd074d5 Mon Sep 17 00:00:00 2001 From: Shoghi Cervantes Date: Thu, 22 May 2014 17:53:56 +0200 Subject: [PATCH 1/3] Level rewrite middle step --- src/pocketmine/entity/Entity.php | 9 + src/pocketmine/event/level/LevelEvent.php | 39 + .../event/level/SpawnChangeEvent.php | 48 + src/pocketmine/level/Level.php | 1384 ++++------------- src/pocketmine/level/Level_OLD.php | 1204 ++++++++++++++ src/pocketmine/level/format/Chunk.php | 23 +- src/pocketmine/level/format/ChunkSection.php | 8 + src/pocketmine/level/format/ChunkSnapshot.php | 24 - .../{LevelFormat.php => LevelProvider.php} | 23 +- src/pocketmine/level/format/anvil/Anvil.php | 4 +- src/pocketmine/level/format/anvil/Chunk.php | 31 + .../level/format/anvil/ChunkSection.php | 16 + .../level/format/anvil/ChunkSnapshot.php | 78 + .../level/format/generic/BaseChunk.php | 23 +- .../format/generic/BaseChunkSnapshot.php | 64 + .../format/generic/BaseLevelProvider.php | 36 + .../format/generic/EmptyChunkSection.php | 16 + 17 files changed, 1919 insertions(+), 1111 deletions(-) create mode 100644 src/pocketmine/event/level/LevelEvent.php create mode 100644 src/pocketmine/event/level/SpawnChangeEvent.php create mode 100644 src/pocketmine/level/Level_OLD.php rename src/pocketmine/level/format/{LevelFormat.php => LevelProvider.php} (88%) create mode 100644 src/pocketmine/level/format/anvil/ChunkSnapshot.php create mode 100644 src/pocketmine/level/format/generic/BaseChunkSnapshot.php create mode 100644 src/pocketmine/level/format/generic/BaseLevelProvider.php diff --git a/src/pocketmine/entity/Entity.php b/src/pocketmine/entity/Entity.php index 7933773e3..62d9ed21f 100644 --- a/src/pocketmine/entity/Entity.php +++ b/src/pocketmine/entity/Entity.php @@ -70,6 +70,15 @@ abstract class Entity extends Position implements Metadatable{ public $passenger = null; public $vehicle = null; + /** @var int */ + public $chunkX; + /** @var int */ + public $chunkZ; + + /** + * TODO: REMOVE + * @var int + */ public $chunkIndex; public $lastX; diff --git a/src/pocketmine/event/level/LevelEvent.php b/src/pocketmine/event/level/LevelEvent.php new file mode 100644 index 000000000..19f780b7b --- /dev/null +++ b/src/pocketmine/event/level/LevelEvent.php @@ -0,0 +1,39 @@ +level; + } +} \ No newline at end of file diff --git a/src/pocketmine/event/level/SpawnChangeEvent.php b/src/pocketmine/event/level/SpawnChangeEvent.php new file mode 100644 index 000000000..735adbd60 --- /dev/null +++ b/src/pocketmine/event/level/SpawnChangeEvent.php @@ -0,0 +1,48 @@ +level = $level; + $this->previousSpawn = $previousSpawn; + } + + /** + * @return Position + */ + public function getPreviousSpawn(){ + return $this->previousSpawn; + } +} \ No newline at end of file diff --git a/src/pocketmine/level/Level.php b/src/pocketmine/level/Level.php index 42a412080..34c2188bf 100644 --- a/src/pocketmine/level/Level.php +++ b/src/pocketmine/level/Level.php @@ -2,11 +2,11 @@ /* * - * ____ _ _ __ __ _ __ __ ____ - * | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \ + * ____ _ _ __ __ _ __ __ ____ + * | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \ * | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) | - * | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/ - * |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_| + * | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/ + * |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_| * * 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 @@ -15,7 +15,7 @@ * * @author PocketMine Team * @link http://www.pocketmine.net/ - * + * * */ @@ -23,39 +23,33 @@ * All Level related classes are here, like Generators, Populators, Noise, ... */ namespace pocketmine\level; - -use pocketmine\block\Air; use pocketmine\block\Block; -use pocketmine\entity\Entity; -use pocketmine\event\block\BlockBreakEvent; -use pocketmine\event\block\BlockPlaceEvent; -use pocketmine\event\player\PlayerInteractEvent; -use pocketmine\item\Item; -use pocketmine\level\generator\Generator; -use pocketmine\math\Vector2; -use pocketmine\math\Vector3 as Vector3; -use pocketmine\nbt\NBT; -use pocketmine\nbt\tag\Compound; -use pocketmine\nbt\tag\Enum; -use pocketmine\nbt\tag\Int; -use pocketmine\nbt\tag\String; -use pocketmine\network\protocol\SetTimePacket; -use pocketmine\network\protocol\UpdateBlockPacket; -use pocketmine\Player; -use pocketmine\level\format\pmf\LevelFormat; +use pocketmine\entity\Human; +use pocketmine\event\level\SpawnChangeEvent; +use pocketmine\level\format\BaseLevelProvider; +use pocketmine\level\format\LevelProvider; +use pocketmine\math\Vector3; use pocketmine\Server; -use pocketmine\tile\Chest; -use pocketmine\tile\Furnace; -use pocketmine\tile\Sign; +use pocketmine\level\generator\Generator; +use pocketmine\level\generator\populator\Populator; +use pocketmine\metadata\Metadatable; +use pocketmine\metadata\MetadataValue; +use pocketmine\plugin\Plugin; +use pocketmine\Player; +use pocketmine\entity\Entity; use pocketmine\tile\Tile; -use pocketmine\utils\Cache; -use pocketmine\utils\Random; -use pocketmine\utils\ReversePriorityQueue; +use pocketmine\level\format\Chunk; + + +class Level implements Metadatable{ + + private static $levelIdCounter = 1; + + /** @var Generator */ + private $generator; + /** @var Populator[] */ + private $populators; -/** - * Main Level handling class, includes all the methods used on them. - */ -class Level{ const BLOCK_UPDATE_NORMAL = 1; const BLOCK_UPDATE_RANDOM = 2; @@ -63,1136 +57,378 @@ class Level{ const BLOCK_UPDATE_WEAK = 4; const BLOCK_UPDATE_TOUCH = 5; - /** @var Player[] */ - public $players = array(); + /** @var \SplObjectStorage */ + protected $players; - /** @var Entity[] */ - public $entities = array(); + /** @var \SplObjectStorage */ + protected $entities; /** @var Entity[][] */ public $chunkEntities = array(); - /** @var Tile[] */ - public $tiles = array(); + /** @var \SplObjectStorage */ + protected $tiles; /** @var Tile[][] */ public $chunkTiles = array(); - public $nextSave; - - /** @var LevelFormat */ - public $level; - public $stopTime; - private $time; - private $startCheck; - private $startTime; /** @var Server */ - private $server; - private $name; - private $usedChunks; - private $changedBlocks; - private $changedCount; - /** @var Generator */ - private $generator; + protected $server; + /** @var int */ + protected $levelId; + /** @var LevelProvider */ + protected $provider; - /** @var ReversePriorityQueue */ - private $updateQueue; + /** @var Player[][] */ + protected $usedChunks = []; - private $autoSave = true; + /** @var Chunk[] */ + protected $unloadQueue; /** - * @param Server $server - * @param LevelFormat $level - * @param string $name + * Returns the chunk unique hash/key + * TODO: return integer values (port from PMF) + * + * @param int $x + * @param int $z + * + * @return string */ - public function __construct(Server $server, LevelFormat $level, $name){ + public static function chunkHash($x, $z){ + return $x .":". $z; + } + + /** + * Init the default level data + * + * @param Server $server + * @param LevelProvider $provider + */ + public function __construct(Server $server, LevelProvider $provider){ + $this->levelId = static::$levelIdCounter++; $this->server = $server; - $this->updateQueue = new ReversePriorityQueue(); - $this->updateQueue->setExtractFlags(\SplPriorityQueue::EXTR_BOTH); - $this->level = $level; - $this->level->level = $this; - $this->startTime = $this->time = (int) $this->level->getData("time"); - $this->nextSave = $this->startCheck = microtime(true); - $this->nextSave += 90; - $this->stopTime = false; - $this->name = $name; - $this->usedChunks = array(); - $this->changedBlocks = array(); - $this->changedCount = array(); - $gen = Generator::getGenerator($this->level->levelData["generator"]); - $this->generator = new $gen((array) $this->level->levelData["generatorSettings"]); - $this->generator->init($this, new Random($this->level->levelData["seed"])); + $this->provider = $provider; + $this->players = new \SplObjectStorage(); + $this->entities = new \SplObjectStorage(); + $this->tiles = new \SplObjectStorage(); } /** - * @return bool + * @return LevelProvider */ - public function getAutoSave(){ - return $this->autoSave === true; + final public function getProvider(){ + return $this->provider; } /** - * @param bool $value - */ - public function setAutoSave($value){ - $this->autoSave = $value; - } - - public function close(){ - $this->__destruct(); - } - - - /** - * Unloads the current level from memory safely + * Returns the unique level identifier * - * @param bool $force default false, force unload of default level + * @return int + */ + final public function getID(){ + return $this->levelId; + } + + /** + * Gets the Block object on the Vector3 location * - * @return bool - */ - public function unload($force = false){ - if($this === $this->server->getDefaultLevel() and $force !== true){ - return false; - } - console("[INFO] Unloading level \"" . $this->getName() . "\""); - $this->nextSave = PHP_INT_MAX; - $this->save(); - $defaultLevel = $this->server->getDefaultLevel(); - foreach($this->getPlayers() as $player){ - if($this === $defaultLevel or $defaultLevel === null){ - $player->close($player->getName() . " has left the game", "forced default level unload"); - }elseif($defaultLevel instanceof Level){ - $player->teleport($this->server->getDefaultLevel()->getSafeSpawn()); - } - } - $this->close(); - if($this === $defaultLevel){ - $this->server->setDefaultLevel(null); - } - - return true; - } - - /** - * Gets the chunks being used by players - * - * @param int $X - * @param int $Z - * - * @return Player[][] - */ - public function getUsingChunk($X, $Z){ - $index = LevelFormat::getIndex($X, $Z); - - return isset($this->usedChunks[$index]) ? $this->usedChunks[$index] : array(); - } - - /** - * WARNING: Do not use this, it's only for internal use. - * Changes to this function won't be recorded on the version. - * - * @param int $X - * @param int $Z - * @param Player $player - */ - public function useChunk($X, $Z, Player $player){ - $index = LevelFormat::getIndex($X, $Z); - $this->loadChunk($X, $Z); - $this->usedChunks[$index][$player->CID] = $player; - } - - /** - * WARNING: Do not use this, it's only for internal use. - * Changes to this function won't be recorded on the version. - * - * @param Player $player - */ - public function freeAllChunks(Player $player){ - foreach($this->usedChunks as $i => $c){ - unset($this->usedChunks[$i][$player->CID]); - } - } - - /** - * WARNING: Do not use this, it's only for internal use. - * Changes to this function won't be recorded on the version. - * - * @param int $X - * @param int $Z - * @param Player $player - */ - public function freeChunk($X, $Z, Player $player){ - unset($this->usedChunks[LevelFormat::getIndex($X, $Z)][$player->CID]); - } - - /** - * @param int $X - * @param int $Z - * - * @return bool - */ - public function isChunkPopulated($X, $Z){ - return $this->level->isPopulated($X, $Z); - } - - /** - * WARNING: Do not use this, it's only for internal use. - * Changes to this function won't be recorded on the version. - */ - public function checkTime(){ - if(!isset($this->level)){ - return; - } - $now = microtime(true); - if($this->stopTime == true){ - return; - }else{ - $time = $this->startTime + ($now - $this->startCheck) * 20; - } - - $this->time = $time; - $pk = new SetTimePacket; - $pk->time = (int) $this->time; - $pk->started = $this->stopTime == false; - Player::broadcastPacket($this->players, $pk); - - return; - } - - /** - * WARNING: Do not use this, it's only for internal use. - * Changes to this function won't be recorded on the version. - * - * @param int $currentTick - * - * @return bool - */ - public function doTick($currentTick){ - if(!isset($this->level)){ - return false; - } - - if(($currentTick % 200) === 0){ - $this->checkTime(); - } - - if($this->level->isGenerating === 0 and count($this->changedCount) > 0){ - foreach($this->changedCount as $index => $mini){ - for($Y = 0; $Y < 8; ++$Y){ - if(($mini & (1 << $Y)) === 0){ - continue; - } - if(count($this->changedBlocks[$index][$Y]) < 582){ //Optimal value, calculated using the relation between minichunks and single packets - continue; - }else{ - foreach($this->players as $p){ - $p->setChunkIndex($index, $mini); - } - unset($this->changedBlocks[$index][$Y]); - } - } - } - $this->changedCount = array(); - - if(count($this->changedBlocks) > 0){ - foreach($this->changedBlocks as $index => $mini){ - foreach($mini as $blocks){ - foreach($blocks as $b){ - $pk = new UpdateBlockPacket; - $pk->x = $b->x; - $pk->y = $b->y; - $pk->z = $b->z; - $pk->block = $b->getID(); - $pk->meta = $b->getMetadata(); - Player::broadcastPacket($this->players, $pk); - } - } - } - $this->changedBlocks = array(); - } - - $X = null; - $Z = null; - - //Do chunk updates - while($this->updateQueue->count() > 0 and $this->updateQueue->current()["priority"] <= $currentTick){ - $block = $this->getBlockRaw($this->updateQueue->extract()["data"]); - $block->onUpdate(self::BLOCK_UPDATE_SCHEDULED); - } - - foreach($this->usedChunks as $index => $p){ - LevelFormat::getXZ($index, $X, $Z); - for($Y = 0; $Y < 8; ++$Y){ - if(!$this->level->isMiniChunkEmpty($X, $Z, $Y)){ - for($i = 0; $i < 3; ++$i){ - $block = $this->getBlockRaw(new Vector3(($X << 4) + mt_rand(0, 15), ($Y << 4) + mt_rand(0, 15), ($Z << 4) + mt_rand(0, 15))); - if($block instanceof Block){ - if($block->onUpdate(self::BLOCK_UPDATE_RANDOM) === self::BLOCK_UPDATE_NORMAL){ - $this->updateAround($block, self::BLOCK_UPDATE_NORMAL); - } - } - } - } - } - } - } - - if($this->nextSave < microtime(true)){ - $X = null; - $Z = null; - foreach($this->usedChunks as $i => $c){ - if(count($c) === 0){ - unset($this->usedChunks[$i]); - LevelFormat::getXZ($i, $X, $Z); - if(!$this->isSpawnChunk($X, $Z)){ - $this->level->unloadChunk($X, $Z, $this->getAutoSave()); - } - } - } - $this->save(false, false); - } - } - - /** - * @param int $X - * @param int $Z - * - * @return bool - */ - public function generateChunk($X, $Z){ - ++$this->level->isGenerating; - $this->generator->generateChunk($X, $Z); - --$this->level->isGenerating; - - return true; - } - - /** - * @param int $X - * @param int $Z - * - * @return bool - */ - public function populateChunk($X, $Z){ - $this->level->setPopulated($X, $Z); - $this->generator->populateChunk($X, $Z); - - return true; - } - - public function __destruct(){ - if(isset($this->level)){ - $this->save(false, false); - $this->level->closeLevel(); - if($this->isLoaded()){ - unset($this->level); - $this->server->unloadLevel($this, true); - } - } - } - - /** - * @return bool - */ - public function isLoaded(){ - return isset($this->level) and $this->level instanceof LevelFormat; - } - - /** - * @param bool $force - * @param bool $extra - * - * @return bool - */ - public function save($force = false, $extra = true){ - if(!isset($this->level)){ - return false; - } - - if($this->getAutoSave() === false and $force === false){ - return; - } - - if($extra !== false){ - $this->doSaveRoundExtra(); - } - - $this->level->setData("time", (int) $this->time); - $this->level->doSaveRound($force); - $this->level->saveData(); - $this->nextSave = microtime(true) + 45; - - return true; - } - - protected function doSaveRoundExtra(){ - foreach($this->usedChunks as $index => $d){ - LevelFormat::getXZ($index, $X, $Z); - $nbt = new Compound("", array( - new Enum("Entities", array()), - new Enum("TileEntities", array()), - )); - $nbt->Entities->setTagType(NBT::TAG_Compound); - $nbt->TileEntities->setTagType(NBT::TAG_Compound); - - $i = 0; - foreach($this->chunkEntities[$index] as $entity){ - if($entity->closed !== true){ - $entity->saveNBT(); - $nbt->Entities[$i] = $entity->namedtag; - ++$i; - } - } - - $i = 0; - foreach($this->chunkTiles[$index] as $tile){ - if($tile->closed !== true){ - $nbt->TileEntities[$i] = $tile->namedtag; - ++$i; - } - } - - $this->level->setChunkNBT($X, $Z, $nbt); - } - } - - /** - * @param Vector3 $pos - * @param int $type - */ - public function updateAround(Vector3 $pos, $type = self::BLOCK_UPDATE_NORMAL){ - $block = $this->getBlockRaw($pos); - $block->getSide(0)->onUpdate($type); - $block->getSide(1)->onUpdate($type); - $block->getSide(2)->onUpdate($type); - $block->getSide(3)->onUpdate($type); - $block->getSide(4)->onUpdate($type); - $block->getSide(5)->onUpdate($type); - } - - /** - * @param Vector3 $pos - * @param int $delay - */ - public function scheduleUpdate(Vector3 $pos, $delay){ - $this->updateQueue->insert($pos, (int) $delay); - } - - /** * @param Vector3 $pos * * @return Block */ - public function getBlockRaw(Vector3 $pos){ - $b = $this->level->getBlock($pos->x, $pos->y, $pos->z); - - return Block::get($b[0], $b[1], new Position($pos->x, $pos->y, $pos->z, $this)); - } - - /** - * @param Vector3 $pos - * - * @return bool|Block - */ public function getBlock(Vector3 $pos){ - if($pos instanceof Position and $pos->level !== $this){ - return false; - } - $b = $this->level->getBlock($pos->x, $pos->y, $pos->z); - - return Block::get($b[0], $b[1], new Position($pos->x, $pos->y, $pos->z, $this)); + $blockId = null; + $meta = null; + $this->getChunkAt($pos->x >> 4, $pos->z >> 4)->getBlock($pos->x & 0x0f, $pos->y & 0x7f, $pos->z & 0x0f, $blockId, $meta); + return Block::get($blockId, $meta, Position::fromObject(clone $pos, $this)); } /** + * Sets on Vector3 the data from a Block object, + * does block updates and puts the changes to the send queue. + * * @param Vector3 $pos * @param Block $block - * @param bool $direct - * @param bool $send - * - * @return bool */ - public function setBlockRaw(Vector3 $pos, Block $block, $direct = true, $send = true){ - if(($ret = $this->level->setBlock($pos->x, $pos->y, $pos->z, $block->getID(), $block->getMetadata())) === true and $send !== false){ - if($direct === true){ - $pk = new UpdateBlockPacket; - $pk->x = $pos->x; - $pk->y = $pos->y; - $pk->z = $pos->z; - $pk->block = $block->getID(); - $pk->meta = $block->getMetadata(); - Player::broadcastPacket($this->players, $pk); - }elseif($direct === false){ - if(!($pos instanceof Position)){ - $pos = new Position($pos->x, $pos->y, $pos->z, $this); - } - $block->position($pos); - $index = LevelFormat::getIndex($pos->x >> 4, $pos->z >> 4); - if(ADVANCED_CACHE == true){ - Cache::remove("world:{$this->name}:{$index}"); - } - if(!isset($this->changedBlocks[$index])){ - $this->changedBlocks[$index] = array(); - $this->changedCount[$index] = 0; - } - $Y = $pos->y >> 4; - if(!isset($this->changedBlocks[$index][$Y])){ - $this->changedBlocks[$index][$Y] = array(); - $this->changedCount[$index] |= 1 << $Y; - } - $this->changedBlocks[$index][$Y][] = clone $block; - } - } - - return $ret; + public function setBlock(Vector3 $pos, Block $block){ + //TODO: handle block setting + //block updates + //block change send queue + //etc. } /** + * Gets the raw block id. + * + * @param int $x + * @param int $y + * @param int $z + * + * @return int 0-255 + */ + public function getBlockIdAt($x, $y, $z){ + return $this->getChunkAt($x >> 4, $z >> 4)->getBlockId($x & 0x0f, $y & 0x7f, $z & 0x0f); + } + + /** + * Sets the raw block id. + * + * @param int $x + * @param int $y + * @param int $z + * @param int $id 0-255 + */ + public function setBlockIdAt($x, $y, $z, $id){ + $this->getChunkAt($x >> 4, $z >> 4)->setBlockId($x & 0x0f, $y & 0x7f, $z & 0x0f, $id & 0xff); + } + + /** + * Gets the raw block metadata + * + * @param int $x + * @param int $y + * @param int $z + * + * @return int 0-15 + */ + public function getBlockDataAt($x, $y, $z){ + return $this->getChunkAt($x >> 4, $z >> 4)->getBlockData($x & 0x0f, $y & 0x7f, $z & 0x0f); + } + + /** + * Sets the raw block metadata. + * + * @param int $x + * @param int $y + * @param int $z + * @param int $data 0-15 + */ + public function setBlockDataAt($x, $y, $z, $data){ + $this->getChunkAt($x >> 4, $z >> 4)->setBlockData($x & 0x0f, $y & 0x7f, $z & 0x0f, $data & 0x0f); + } + + /** + * Gets the raw block skylight level + * + * @param int $x + * @param int $y + * @param int $z + * + * @return int 0-15 + */ + public function getBlockSkyLightAt($x, $y, $z){ + return $this->getChunkAt($x >> 4, $z >> 4)->getBlockSkyLight($x & 0x0f, $y & 0x7f, $z & 0x0f); + } + + /** + * Sets the raw block skylight level. + * + * @param int $x + * @param int $y + * @param int $z + * @param int $level 0-15 + */ + public function setBlockSkyLightAt($x, $y, $z, $level){ + $this->getChunkAt($x >> 4, $z >> 4)->setBlockSkyLight($x & 0x0f, $y & 0x7f, $z & 0x0f, $level & 0x0f); + } + + /** + * Gets the raw block light level + * + * @param int $x + * @param int $y + * @param int $z + * + * @return int 0-15 + */ + public function getBlockLightAt($x, $y, $z){ + return $this->getChunkAt($x >> 4, $z >> 4)->getBlockLight($x & 0x0f, $y & 0x7f, $z & 0x0f); + } + + /** + * Sets the raw block light level. + * + * @param int $x + * @param int $y + * @param int $z + * @param int $level 0-15 + */ + public function setBlockLightAt($x, $y, $z, $level){ + $this->getChunkAt($x >> 4, $z >> 4)->setBlockLight($x & 0x0f, $y & 0x7f, $z & 0x0f, $level & 0x0f); + } + + /** + * Gets the Chunk object + * + * @param int $x + * @param int $z + * @param bool $create Whether to generate the chunk if it does not exist + * + * @return Chunk + */ + protected function getChunkAt($x, $z, $create = false){ + $this->provider->getChunk($x, $z, $create); + } + + /** + * Gets the highest block Y value at a specific $x and $z + * + * @param int $x + * @param int $z + * + * @return int 0-127 + */ + public function getHighestBlockAt($x, $z){ + if(!$this->isChunkLoaded($x >> 4, $z >> 4)){ + $this->loadChunk($x >> 4, $z >> 4); + } + + return $this->getChunkAt($x >> 4, $z >> 4)->getHighestBlockAt($x & 0x0f, $z & 0x0f); + } + + /** + * @param int $x + * @param int $z + * @return bool + */ + public function isChunkLoaded($x, $z){ + //TODO + return false; + } + + /** + * Returns a Position pointing to the spawn + * + * @return Position + */ + public function getSpawnLocation(){ + return Position::fromObject($this->provider->getSpawn(), $this); + } + + /** + * Sets the level spawn location + * * @param Vector3 $pos - * @param Block $block - * @param bool $update - * @param bool $tiles - * @param bool $direct - * - * @return bool */ - public function setBlock(Vector3 $pos, Block $block, $update = true, $tiles = false, $direct = false){ - if((($pos instanceof Position) and $pos->level !== $this) or $pos->x < 0 or $pos->y < 0 or $pos->z < 0){ - return false; - } - - $ret = $this->level->setBlock($pos->x, $pos->y, $pos->z, $block->getID(), $block->getMetadata()); - if($ret === true){ - if(!($pos instanceof Position)){ - $pos = new Position($pos->x, $pos->y, $pos->z, $this); - } - $block->position($pos); - - if($direct === true){ - $pk = new UpdateBlockPacket; - $pk->x = $pos->x; - $pk->y = $pos->y; - $pk->z = $pos->z; - $pk->block = $block->getID(); - $pk->meta = $block->getMetadata(); - Player::broadcastPacket($this->players, $pk); - }else{ - $index = LevelFormat::getIndex($pos->x >> 4, $pos->z >> 4); - if(ADVANCED_CACHE == true){ - Cache::remove("world:{$this->name}:{$index}"); - } - if(!isset($this->changedBlocks[$index])){ - $this->changedBlocks[$index] = array(); - $this->changedCount[$index] = 0; - } - $Y = $pos->y >> 4; - if(!isset($this->changedBlocks[$index][$Y])){ - $this->changedBlocks[$index][$Y] = array(); - $this->changedCount[$index] |= 1 << $Y; - } - $this->changedBlocks[$index][$Y][] = clone $block; - } - - if($update === true){ - $this->updateAround($pos, self::BLOCK_UPDATE_NORMAL); - $block->onUpdate(self::BLOCK_UPDATE_NORMAL); - } - if($tiles === true){ - if(($t = $this->getTile($pos)) instanceof Tile){ - $t->close(); - } - } - } - - return $ret; + public function setSpawnLocation(Vector3 $pos){ + $previousSpawn = $this->getSpawnLocation(); + $this->provider->setSpawn($pos); + $this->server->getPluginManager()->callEvent(new SpawnChangeEvent($this, $previousSpawn)); } /** - * Tries to break a block using a item, including Player time checks if available + * Removes the entity from the level index * - * @param Vector3 $vector - * @param Item &$item (if null, can break anything) - * @param Player $player + * @param Entity $entity * - * @return boolean + * @throws \RuntimeException */ - public function useBreakOn(Vector3 $vector, Item &$item = null, Player $player = null){ - $target = $this->getBlock($vector); - - if($player instanceof Player){ - $lastTime = $player->lastBreak - $player->getLag() / 1000; - if(($player->getGamemode() & 0x01) === 1 and ($lastTime + 0.15) >= microtime(true)){ - return false; - }elseif(($lastTime + $target->getBreakTime($item)) >= microtime(true)){ - return false; - } - $player->lastBreak = microtime(true); + public function removeEntity(Entity $entity){ + if($entity->getLevel() !== $this){ + throw new \RuntimeException("Invalid Entity level"); + } + $entity->kill(); + if($entity instanceof Player){ + $this->players->detach($entity); + //$this->everyoneSleeping(); } - //TODO: Adventure mode checks - - if($player instanceof Player){ - $ev = new BlockBreakEvent($player, $target, $item, ($player->getGamemode() & 0x01) === 1 ? true : false); - if($item instanceof Item and !$target->isBreakable($item) and $ev->getInstaBreak() === false){ - $ev->setCancelled(); - } - if(!$player->isOp() and ($distance = $this->server->getConfigInt("spawn-protection", 16)) > -1){ - $t = new Vector2($target->x, $target->z); - $s = new Vector2($this->getSpawn()->x, $this->getSpawn()->z); - if($t->distance($s) <= $distance){ //set it to cancelled so plugins can bypass this - $ev->setCancelled(); - } - } - $this->server->getPluginManager()->callEvent($ev); - if($ev->isCancelled()){ - return false; - } - }elseif($item instanceof Item and !$target->isBreakable($item)){ - return false; + if($this->isChunkLoaded($entity->chunkX, $entity->chunkZ)){ + $this->getChunkAt($entity->chunkX, $entity->chunkZ)->removeEntity($entity); } - $target->onBreak($item); - if($item instanceof Item){ - $item->useOn($target); - if($item->isTool() and $item->getMetadata() >= $item->getMaxDurability()){ - $item = Item::get(Item::AIR, 0, 0); - } - } - - return $target->getDrops($item); + $this->entities->detach($entity); } - /** - * Uses a item on a position and face, placing it or activating the block - * - * @param Vector3 $vector - * @param Item $item - * @param int $face - * @param float $fx default 0.0 - * @param float $fy default 0.0 - * @param float $fz default 0.0 - * @param Player $player default null - * - * @return boolean - */ - public function useItemOn(Vector3 $vector, Item &$item, $face, $fx = 0.0, $fy = 0.0, $fz = 0.0, Player $player = null){ - $target = $this->getBlock($vector); - $block = $target->getSide($face); + public function isChunkInUse($x, $z){ + return isset($this->usedChunks[static::chunkHash($x, $z)]); + } - if($block->y > 127 or $block->y < 0){ - return false; + public function loadChunk($x, $z, $generate = true){ + if($generate === true){ + return $this->getChunkAt($x, $z, true) instanceof Chunk; } - if($target->getID() === Item::AIR){ - return false; - } + $this->cancelUnloadChunkRequest($x, $z); - if($player instanceof Player){ - $ev = new PlayerInteractEvent($player, $item, $target, $face); - if(!$player->isOp() and ($distance = $this->server->getConfigInt("spawn-protection", 16)) > -1){ - $t = new Vector2($target->x, $target->z); - $s = new Vector2($this->getSpawn()->x, $this->getSpawn()->z); - if($t->distance($s) <= $distance){ //set it to cancelled so plugins can bypass this - $ev->setCancelled(); - } - } - $this->server->getPluginManager()->callEvent($ev); - if(!$ev->isCancelled()){ - $target->onUpdate(self::BLOCK_UPDATE_TOUCH); - if($target->isActivable === true and $target->onActivate($item, $player) === true){ - return true; - } - } - }elseif($target->isActivable === true and $target->onActivate($item, $player) === true){ + $chunk = $this->provider->getChunk($x, $z, false); + if($chunk instanceof Chunk){ return true; - } - - if($item->isPlaceable()){ - $hand = $item->getBlock(); - $hand->position($block); - }elseif($block->getID() === Item::FIRE){ - $this->setBlock($block, new Air(), true, false, true); - - return false; }else{ + $this->provider->loadChunk($x, $z); + return $this->provider->getChunk($x, $z) instanceof Chunk; + } + } + + + protected function queueUnloadChunk($x, $z){ + //TODO + } + + public function unloadChunkRequest($x, $z, $safe = true){ + if($safe === true and $this->isChunkInUse($x, $z)){ return false; } - if(!($block->isReplaceable === true or ($hand->getID() === Item::SLAB and $block->getID() === Item::SLAB))){ - return false; - } - - if($target->isReplaceable === true){ - $block = $target; - $hand->position($block); - //$face = -1; - } - - //TODO: Implement using Bounding Boxes, all entities - /*if($hand->isSolid === true and $player->inBlock($block)){ - return false; //Entity in block - }*/ - - - if($player instanceof Player){ - $ev = new BlockPlaceEvent($player, $hand, $block, $target, $item); - if(!$player->isOp() and ($distance = $this->server->getConfigInt("spawn-protection", 16)) > -1){ - $t = new Vector2($target->x, $target->z); - $s = new Vector2($this->getSpawn()->x, $this->getSpawn()->z); - if($t->distance($s) <= $distance){ //set it to cancelled so plugins can bypass this - $ev->setCancelled(); - } - } - $this->server->getPluginManager()->callEvent($ev); - if($ev->isCancelled()){ - return false; - } - } - - if($hand->place($item, $block, $target, $face, $fx, $fy, $fz, $player) === false){ - return false; - } - - if($hand->getID() === Item::SIGN_POST or $hand->getID() === Item::WALL_SIGN){ - $tile = new Sign($this, new Compound(false, array( - new String("id", Tile::SIGN), - new Int("x", $block->x), - new Int("y", $block->y), - new Int("z", $block->z), - new String("Text1", ""), - new String("Text2", ""), - new String("Text3", ""), - new String("Text4", "") - ))); - if($player instanceof Player){ - $tile->namedtag->creator = new String("creator", $player->getName()); - } - } - $item->setCount($item->getCount() - 1); - if($item->getCount() <= 0){ - $item = Item::get(Item::AIR, 0, 0); - } + $this->queueUnloadChunk($x, $z); return true; } - /** - * Gets the biome ID of a column - * - * @param int $x - * @param int $z - * - * @return int - */ - public function getBiome($x, $z){ - return $this->level->getBiome((int) $x, (int) $z); + public function cancelUnloadChunkRequest($x, $z){ + unset($this->unloadQueue[static::chunkHash($x, $z)]); } - /** - * Sets the biome ID for a column - * - * @param int $x - * @param int $z - * @param int $biome - * - * @return int - */ - public function setBiome($x, $z, $biome){ - return $this->level->getBiome((int) $x, (int) $z, $biome); - } - - /** - * Gets the list of all the entitites in this level - * - * @return Entity[] - */ - public function getEntities(){ - return $this->entities; - } - - /** - * Returns a list of the Tile entities in this level - * - * @return Tile[] - */ - public function getTiles(){ - return $this->tiles; - } - - /** - * Returns a list of the players in this level - * - * @return Player[] - */ - public function getPlayers(){ - return $this->players; - } - - /** - * Returns the Tile in a position, or false if not found - * - * @param Vector3 $pos - * - * @return bool|Tile - */ - public function getTile(Vector3 $pos){ - if($pos instanceof Position and $pos->level !== $this){ - return false; - } - $tiles = $this->getChunkTiles($pos->x >> 4, $pos->z >> 4); - if(count($tiles) > 0){ - foreach($tiles as $tile){ - if($tile->x === (int) $pos->x and $tile->y === (int) $pos->y and $tile->z === (int) $pos->z){ - return $tile; - } - } - } - - return false; - } - - /** - * Gets a raw minichunk - * - * @param int $X - * @param int $Z - * @param int $Y - * - * @return string - */ - public function getMiniChunk($X, $Z, $Y){ - return $this->level->getMiniChunk($X, $Z, $Y); - } - - /** - * Sets a raw minichunk - * - * @param int $X - * @param int $Z - * @param int $Y - * @param string $data (must be 4096 bytes) - * - * @return bool - */ - public function setMiniChunk($X, $Z, $Y, $data){ - $this->changedCount[$X . ":" . $Y . ":" . $Z] = 4096; - if(ADVANCED_CACHE == true){ - Cache::remove("world:{$this->name}:$X:$Z"); - } - - return $this->level->setMiniChunk($X, $Z, $Y, $data); - } - - /** - * Returns a list of the entities on a given chunk - * - * @param int $X - * @param int $Z - * - * @return Entity[] - */ - public function getChunkEntities($X, $Z){ - $index = LevelFormat::getIndex($X, $Z); - if(isset($this->usedChunks[$index]) or $this->loadChunk($X, $Z) === true){ - return $this->chunkEntities[$index]; - } - - return array(); - } - - /** - * Gives a list of the Tile entities on a given chunk - * - * @param int $X - * @param int $Z - * - * @return Tile[] - */ - public function getChunkTiles($X, $Z){ - $index = LevelFormat::getIndex($X, $Z); - if(isset($this->usedChunks[$index]) or $this->loadChunk($X, $Z) === true){ - return $this->chunkTiles[$index]; - } - - return array(); - } - - /** - * Loads a chunk - * - * @param int $X - * @param int $Z - * - * @return bool - */ - public function loadChunk($X, $Z){ - $index = LevelFormat::getIndex($X, $Z); - if(isset($this->usedChunks[$index])){ - return true; - }elseif($this->level->loadChunk($X, $Z) !== false){ - $this->usedChunks[$index] = array(); - $this->chunkTiles[$index] = array(); - $this->chunkEntities[$index] = array(); - $tags = $this->level->getChunkNBT($X, $Z); - if(isset($tags->Entities)){ - foreach($tags->Entities as $nbt){ - if(!isset($nbt["id"])){ - continue; - } - switch($nbt["id"]){ - //TODO: spawn entities - } - } - } - if(isset($tags->TileEntities)){ - foreach($tags->TileEntities as $nbt){ - switch($nbt["id"]){ - case Tile::CHEST: - new Chest($this, $nbt); - break; - case Tile::FURNACE: - new Furnace($this, $nbt); - break; - case Tile::SIGN: - new Sign($this, $nbt); - break; - } - } - } - - return true; - } - - return false; - } - - /** - * Unloads a chunk - * - * @param int $X - * @param int $Z - * @param bool $force - * - * @return bool - */ - public function unloadChunk($X, $Z, $force = false){ - if(!isset($this->level)){ + public function unloadChunk($x, $z, $safe = true){ + if($safe === true and $this->isChunkInUse($x, $z)){ return false; } - if($force !== true and $this->isSpawnChunk($X, $Z)){ - return false; - } - $index = LevelFormat::getIndex($X, $Z); - unset($this->usedChunks[$index]); - unset($this->chunkEntities[$index]); - unset($this->chunkTiles[$index]); - Cache::remove("world:{$this->name}:$X:$Z"); - return $this->level->unloadChunk($X, $Z, $this->getAutoSave()); + + $this->provider->unloadChunk($x, $z); + + return true; } - /** - * Returns true if the spawn is part of the spawn - * - * @param int $X - * @param int $Z - * - * @return bool - */ - public function isSpawnChunk($X, $Z){ - $spawnX = $this->level->getData("spawnX") >> 4; - $spawnZ = $this->level->getData("spawnZ") >> 4; - - return abs($X - $spawnX) <= 1 and abs($Z - $spawnZ) <= 1; + public function generateChunk($x, $z){ + //TODO } - /** - * Gets a full chunk or parts of it for networking usage, allows cache usage - * - * @param int $X - * @param int $Z - * @param int $Yndex bitmap of chunks to be returned - * - * @return bool|mixed|string - */ - public function getOrderedChunk($X, $Z, $Yndex){ - if(!isset($this->level)){ - return false; - } - if(ADVANCED_CACHE == true and $Yndex === 0xff){ - $identifier = "world:{$this->name}:" . LevelFormat::getIndex($X, $Z); - if(($cache = Cache::get($identifier)) !== false){ - return $cache; - } - } + public function regenerateChunk($x, $z){ + $this->unloadChunk($x, $z); + $this->cancelUnloadChunkRequest($x, $z); - $raw = array(); - for($Y = 0; $Y < 8; ++$Y){ - if(($Yndex & (1 << $Y)) !== 0){ - $raw[$Y] = $this->level->getMiniChunk($X, $Z, $Y); - } - } - - $ordered = ""; - $flag = chr($Yndex); - for($j = 0; $j < 256; ++$j){ - $ordered .= $flag; - foreach($raw as $mini){ - $ordered .= substr($mini, $j << 5, 24); //16 + 8 - } - } - if(ADVANCED_CACHE == true and $Yndex == 0xff){ - Cache::add($identifier, $ordered, 60); - } - - return $ordered; + //TODO: generate & refresh chunk from the generator object } - /** - * Returns the network minichunk for a given Y - * - * @param int $X - * @param int $Z - * @param int $Y - * - * @return bool|string - */ - public function getOrderedMiniChunk($X, $Z, $Y){ - if(!isset($this->level)){ - return false; - } - $raw = $this->level->getMiniChunk($X, $Z, $Y); - $ordered = ""; - $flag = chr(1 << $Y); - for($j = 0; $j < 256; ++$j){ - $ordered .= $flag . substr($raw, $j << 5, 24); //16 + 8 - } + public function doChunkGarbageCollection(){ + if(count($this->unloadQueue) > 0){ + foreach($this->unloadQueue as $index => $chunk){ - return $ordered; - } - - /** - * Returns the raw spawnpoint - * - * @return Position - */ - public function getSpawn(){ - return new Position($this->level->getData("spawnX"), $this->level->getData("spawnY"), $this->level->getData("spawnZ"), $this); - } - - /** - * @param Vector3 $spawn default null - * - * @return bool|Position - */ - public function getSafeSpawn($spawn = null){ - if(!($spawn instanceof Vector3)){ - $spawn = $this->getSpawn(); - } - if($spawn instanceof Vector3){ - $x = (int) round($spawn->x); - $y = (int) round($spawn->y); - $z = (int) round($spawn->z); - for(; $y > 0; --$y){ - $v = new Vector3($x, $y, $z); - $b = $this->getBlock($v->getSide(0)); - if($b === false){ - return $spawn; - }elseif(!($b instanceof Air)){ - break; + //If the chunk can't be unloaded, it stays on the queue + if($this->unloadChunk($chunk->getX(), $chunk->getZ(), true)){ + unset($this->unloadQueue[$index]); } } - for(; $y < 128; ++$y){ - $v = new Vector3($x, $y, $z); - if($this->getBlock($v->getSide(1)) instanceof Air){ - if($this->getBlock($v) instanceof Air){ - return new Position($x, $y, $z, $this); - } - }else{ - ++$y; - } - } - - return new Position($x, $y, $z, $this); } - - return false; } - /** - * Sets the spawnpoint - * - * @param Vector3 $pos - */ - public function setSpawn(Vector3 $pos){ - $this->level->setData("spawnX", $pos->x); - $this->level->setData("spawnY", $pos->y); - $this->level->setData("spawnZ", $pos->z); + + public function setMetadata($metadataKey, MetadataValue $metadataValue){ + $this->server->getPlayerMetadata()->setMetadata($this, $metadataKey, $metadataValue); } - /** - * Gets the current time - * - * @return int - */ - public function getTime(){ - return (int) $this->time; + public function getMetadata($metadataKey){ + return $this->server->getPlayerMetadata()->getMetadata($this, $metadataKey); } - /** - * Returns the Level name - * - * @return string - */ - public function getName(){ - return $this->name; //return $this->level->getData("name"); + public function hasMetadata($metadataKey){ + return $this->server->getPlayerMetadata()->hasMetadata($this, $metadataKey); } - /** - * Sets the current time on the level - * - * @param int $time - */ - public function setTime($time){ - $this->startTime = $this->time = (int) $time; - $this->startCheck = microtime(true); - $this->checkTime(); + public function removeMetadata($metadataKey, Plugin $plugin){ + $this->server->getPlayerMetadata()->removeMetadata($this, $metadataKey, $plugin); } - - /** - * Stops the time for the level, will not save the lock state to disk - */ - public function stopTime(){ - $this->stopTime = true; - $this->startCheck = 0; - $this->checkTime(); - } - - /** - * Start the time again, if it was stopped - */ - public function startTime(){ - $this->stopTime = false; - $this->startCheck = microtime(true); - $this->checkTime(); - } - - /** - * Gets the level seed - * - * @return int - */ - public function getSeed(){ - return (int) $this->level->getData("seed"); - } - - /** - * Sets the seed for the level - * - * @param int $seed - */ - public function setSeed($seed){ - $this->level->setData("seed", (int) $seed); - } -} +} \ No newline at end of file diff --git a/src/pocketmine/level/Level_OLD.php b/src/pocketmine/level/Level_OLD.php new file mode 100644 index 000000000..d50a5d2a1 --- /dev/null +++ b/src/pocketmine/level/Level_OLD.php @@ -0,0 +1,1204 @@ +server = $server; + $this->updateQueue = new ReversePriorityQueue(); + $this->updateQueue->setExtractFlags(\SplPriorityQueue::EXTR_BOTH); + $this->level->level = $this; + $this->nextSave = $this->startCheck = microtime(true); + $this->nextSave += 90; + $this->stopTime = false; + $this->usedChunks = array(); + $this->changedBlocks = array(); + $this->changedCount = array(); + $gen = Generator::getGenerator($this->level->levelData["generator"]); + $this->generator = new $gen((array) $this->level->levelData["generatorSettings"]); + $this->generator->init($this, new Random($this->level->levelData["seed"])); + } + + protected function init(){ + + } + + public function getPath(){ + return $this->path; + } + + /** + * @return bool + */ + public function getAutoSave(){ + return $this->autoSave === true; + } + + /** + * @param bool $value + */ + public function setAutoSave($value){ + $this->autoSave = $value; + } + + public function close(){ + $this->__destruct(); + } + + + /** + * Unloads the current level from memory safely + * + * @param bool $force default false, force unload of default level + * + * @return bool + */ + public function unload($force = false){ + if($this === $this->server->getDefaultLevel() and $force !== true){ + return false; + } + console("[INFO] Unloading level \"" . $this->getName() . "\""); + $this->nextSave = PHP_INT_MAX; + $this->save(); + $defaultLevel = $this->server->getDefaultLevel(); + foreach($this->getPlayers() as $player){ + if($this === $defaultLevel or $defaultLevel === null){ + $player->close($player->getName() . " has left the game", "forced default level unload"); + }elseif($defaultLevel instanceof Level){ + $player->teleport($this->server->getDefaultLevel()->getSafeSpawn()); + } + } + $this->close(); + if($this === $defaultLevel){ + $this->server->setDefaultLevel(null); + } + + return true; + } + + /** + * Gets the chunks being used by players + * + * @param int $X + * @param int $Z + * + * @return Player[][] + */ + public function getUsingChunk($X, $Z){ + $index = LevelFormat::getIndex($X, $Z); + + return isset($this->usedChunks[$index]) ? $this->usedChunks[$index] : array(); + } + + /** + * WARNING: Do not use this, it's only for internal use. + * Changes to this function won't be recorded on the version. + * + * @param int $X + * @param int $Z + * @param Player $player + */ + public function useChunk($X, $Z, Player $player){ + $index = LevelFormat::getIndex($X, $Z); + $this->loadChunk($X, $Z); + $this->usedChunks[$index][$player->CID] = $player; + } + + /** + * WARNING: Do not use this, it's only for internal use. + * Changes to this function won't be recorded on the version. + * + * @param Player $player + */ + public function freeAllChunks(Player $player){ + foreach($this->usedChunks as $i => $c){ + unset($this->usedChunks[$i][$player->CID]); + } + } + + /** + * WARNING: Do not use this, it's only for internal use. + * Changes to this function won't be recorded on the version. + * + * @param int $X + * @param int $Z + * @param Player $player + */ + public function freeChunk($X, $Z, Player $player){ + unset($this->usedChunks[LevelFormat::getIndex($X, $Z)][$player->CID]); + } + + /** + * @param int $X + * @param int $Z + * + * @return bool + */ + public function isChunkPopulated($X, $Z){ + return $this->level->isPopulated($X, $Z); + } + + /** + * WARNING: Do not use this, it's only for internal use. + * Changes to this function won't be recorded on the version. + */ + public function checkTime(){ + if(!isset($this->level)){ + return; + } + $now = microtime(true); + if($this->stopTime == true){ + return; + }else{ + $time = $this->startTime + ($now - $this->startCheck) * 20; + } + + $this->time = $time; + $pk = new SetTimePacket; + $pk->time = (int) $this->time; + $pk->started = $this->stopTime == false; + Player::broadcastPacket($this->players, $pk); + + return; + } + + /** + * WARNING: Do not use this, it's only for internal use. + * Changes to this function won't be recorded on the version. + * + * @param int $currentTick + * + * @return bool + */ + public function doTick($currentTick){ + if(!isset($this->level)){ + return false; + } + + if(($currentTick % 200) === 0){ + $this->checkTime(); + } + + if($this->level->isGenerating === 0 and count($this->changedCount) > 0){ + foreach($this->changedCount as $index => $mini){ + for($Y = 0; $Y < 8; ++$Y){ + if(($mini & (1 << $Y)) === 0){ + continue; + } + if(count($this->changedBlocks[$index][$Y]) < 582){ //Optimal value, calculated using the relation between minichunks and single packets + continue; + }else{ + foreach($this->players as $p){ + $p->setChunkIndex($index, $mini); + } + unset($this->changedBlocks[$index][$Y]); + } + } + } + $this->changedCount = array(); + + if(count($this->changedBlocks) > 0){ + foreach($this->changedBlocks as $index => $mini){ + foreach($mini as $blocks){ + foreach($blocks as $b){ + $pk = new UpdateBlockPacket; + $pk->x = $b->x; + $pk->y = $b->y; + $pk->z = $b->z; + $pk->block = $b->getID(); + $pk->meta = $b->getMetadata(); + Player::broadcastPacket($this->players, $pk); + } + } + } + $this->changedBlocks = array(); + } + + $X = null; + $Z = null; + + //Do chunk updates + while($this->updateQueue->count() > 0 and $this->updateQueue->current()["priority"] <= $currentTick){ + $block = $this->getBlockRaw($this->updateQueue->extract()["data"]); + $block->onUpdate(self::BLOCK_UPDATE_SCHEDULED); + } + + foreach($this->usedChunks as $index => $p){ + LevelFormat::getXZ($index, $X, $Z); + for($Y = 0; $Y < 8; ++$Y){ + if(!$this->level->isMiniChunkEmpty($X, $Z, $Y)){ + for($i = 0; $i < 3; ++$i){ + $block = $this->getBlockRaw(new Vector3(($X << 4) + mt_rand(0, 15), ($Y << 4) + mt_rand(0, 15), ($Z << 4) + mt_rand(0, 15))); + if($block instanceof Block){ + if($block->onUpdate(self::BLOCK_UPDATE_RANDOM) === self::BLOCK_UPDATE_NORMAL){ + $this->updateAround($block, self::BLOCK_UPDATE_NORMAL); + } + } + } + } + } + } + } + + if($this->nextSave < microtime(true)){ + $X = null; + $Z = null; + foreach($this->usedChunks as $i => $c){ + if(count($c) === 0){ + unset($this->usedChunks[$i]); + LevelFormat::getXZ($i, $X, $Z); + if(!$this->isSpawnChunk($X, $Z)){ + $this->level->unloadChunk($X, $Z, $this->getAutoSave()); + } + } + } + $this->save(false, false); + } + } + + /** + * @param int $X + * @param int $Z + * + * @return bool + */ + public function generateChunk($X, $Z){ + ++$this->level->isGenerating; + $this->generator->generateChunk($X, $Z); + --$this->level->isGenerating; + + return true; + } + + /** + * @param int $X + * @param int $Z + * + * @return bool + */ + public function populateChunk($X, $Z){ + $this->level->setPopulated($X, $Z); + $this->generator->populateChunk($X, $Z); + + return true; + } + + public function __destruct(){ + if(isset($this->level)){ + $this->save(false, false); + $this->level->closeLevel(); + if($this->isLoaded()){ + unset($this->level); + $this->server->unloadLevel($this, true); + } + } + } + + /** + * @return bool + */ + public function isLoaded(){ + return isset($this->level) and $this->level instanceof LevelFormat; + } + + /** + * @param bool $force + * @param bool $extra + * + * @return bool + */ + public function save($force = false, $extra = true){ + if(!isset($this->level)){ + return false; + } + + if($this->getAutoSave() === false and $force === false){ + return; + } + + if($extra !== false){ + $this->doSaveRoundExtra(); + } + + $this->level->setData("time", (int) $this->time); + $this->level->doSaveRound($force); + $this->level->saveData(); + $this->nextSave = microtime(true) + 45; + + return true; + } + + protected function doSaveRoundExtra(){ + foreach($this->usedChunks as $index => $d){ + LevelFormat::getXZ($index, $X, $Z); + $nbt = new Compound("", array( + new Enum("Entities", array()), + new Enum("TileEntities", array()), + )); + $nbt->Entities->setTagType(NBT::TAG_Compound); + $nbt->TileEntities->setTagType(NBT::TAG_Compound); + + $i = 0; + foreach($this->chunkEntities[$index] as $entity){ + if($entity->closed !== true){ + $entity->saveNBT(); + $nbt->Entities[$i] = $entity->namedtag; + ++$i; + } + } + + $i = 0; + foreach($this->chunkTiles[$index] as $tile){ + if($tile->closed !== true){ + $nbt->TileEntities[$i] = $tile->namedtag; + ++$i; + } + } + + $this->level->setChunkNBT($X, $Z, $nbt); + } + } + + /** + * @param Vector3 $pos + * @param int $type + */ + public function updateAround(Vector3 $pos, $type = self::BLOCK_UPDATE_NORMAL){ + $block = $this->getBlockRaw($pos); + $block->getSide(0)->onUpdate($type); + $block->getSide(1)->onUpdate($type); + $block->getSide(2)->onUpdate($type); + $block->getSide(3)->onUpdate($type); + $block->getSide(4)->onUpdate($type); + $block->getSide(5)->onUpdate($type); + } + + /** + * @param Vector3 $pos + * @param int $delay + */ + public function scheduleUpdate(Vector3 $pos, $delay){ + $this->updateQueue->insert($pos, (int) $delay); + } + + /** + * @param Vector3 $pos + * + * @return Block + */ + public function getBlockRaw(Vector3 $pos){ + $b = $this->level->getBlock($pos->x, $pos->y, $pos->z); + + return Block::get($b[0], $b[1], new Position($pos->x, $pos->y, $pos->z, $this)); + } + + /** + * @param Vector3 $pos + * + * @return bool|Block + */ + public function getBlock(Vector3 $pos){ + if($pos instanceof Position and $pos->level !== $this){ + return false; + } + $b = $this->level->getBlock($pos->x, $pos->y, $pos->z); + + return Block::get($b[0], $b[1], new Position($pos->x, $pos->y, $pos->z, $this)); + } + + /** + * @param Vector3 $pos + * @param Block $block + * @param bool $direct + * @param bool $send + * + * @return bool + */ + public function setBlockRaw(Vector3 $pos, Block $block, $direct = true, $send = true){ + if(($ret = $this->level->setBlock($pos->x, $pos->y, $pos->z, $block->getID(), $block->getMetadata())) === true and $send !== false){ + if($direct === true){ + $pk = new UpdateBlockPacket; + $pk->x = $pos->x; + $pk->y = $pos->y; + $pk->z = $pos->z; + $pk->block = $block->getID(); + $pk->meta = $block->getMetadata(); + Player::broadcastPacket($this->players, $pk); + }elseif($direct === false){ + if(!($pos instanceof Position)){ + $pos = new Position($pos->x, $pos->y, $pos->z, $this); + } + $block->position($pos); + $index = LevelFormat::getIndex($pos->x >> 4, $pos->z >> 4); + if(ADVANCED_CACHE == true){ + Cache::remove("world:{$this->name}:{$index}"); + } + if(!isset($this->changedBlocks[$index])){ + $this->changedBlocks[$index] = array(); + $this->changedCount[$index] = 0; + } + $Y = $pos->y >> 4; + if(!isset($this->changedBlocks[$index][$Y])){ + $this->changedBlocks[$index][$Y] = array(); + $this->changedCount[$index] |= 1 << $Y; + } + $this->changedBlocks[$index][$Y][] = clone $block; + } + } + + return $ret; + } + + /** + * @param Vector3 $pos + * @param Block $block + * @param bool $update + * @param bool $tiles + * @param bool $direct + * + * @return bool + */ + public function setBlock(Vector3 $pos, Block $block, $update = true, $tiles = false, $direct = false){ + if((($pos instanceof Position) and $pos->level !== $this) or $pos->x < 0 or $pos->y < 0 or $pos->z < 0){ + return false; + } + + $ret = $this->level->setBlock($pos->x, $pos->y, $pos->z, $block->getID(), $block->getMetadata()); + if($ret === true){ + if(!($pos instanceof Position)){ + $pos = new Position($pos->x, $pos->y, $pos->z, $this); + } + $block->position($pos); + + if($direct === true){ + $pk = new UpdateBlockPacket; + $pk->x = $pos->x; + $pk->y = $pos->y; + $pk->z = $pos->z; + $pk->block = $block->getID(); + $pk->meta = $block->getMetadata(); + Player::broadcastPacket($this->players, $pk); + }else{ + $index = LevelFormat::getIndex($pos->x >> 4, $pos->z >> 4); + if(ADVANCED_CACHE == true){ + Cache::remove("world:{$this->name}:{$index}"); + } + if(!isset($this->changedBlocks[$index])){ + $this->changedBlocks[$index] = array(); + $this->changedCount[$index] = 0; + } + $Y = $pos->y >> 4; + if(!isset($this->changedBlocks[$index][$Y])){ + $this->changedBlocks[$index][$Y] = array(); + $this->changedCount[$index] |= 1 << $Y; + } + $this->changedBlocks[$index][$Y][] = clone $block; + } + + if($update === true){ + $this->updateAround($pos, self::BLOCK_UPDATE_NORMAL); + $block->onUpdate(self::BLOCK_UPDATE_NORMAL); + } + if($tiles === true){ + if(($t = $this->getTile($pos)) instanceof Tile){ + $t->close(); + } + } + } + + return $ret; + } + + /** + * Tries to break a block using a item, including Player time checks if available + * + * @param Vector3 $vector + * @param Item &$item (if null, can break anything) + * @param Player $player + * + * @return boolean + */ + public function useBreakOn(Vector3 $vector, Item &$item = null, Player $player = null){ + $target = $this->getBlock($vector); + + if($player instanceof Player){ + $lastTime = $player->lastBreak - $player->getLag() / 1000; + if(($player->getGamemode() & 0x01) === 1 and ($lastTime + 0.15) >= microtime(true)){ + return false; + }elseif(($lastTime + $target->getBreakTime($item)) >= microtime(true)){ + return false; + } + $player->lastBreak = microtime(true); + } + + //TODO: Adventure mode checks + + if($player instanceof Player){ + $ev = new BlockBreakEvent($player, $target, $item, ($player->getGamemode() & 0x01) === 1 ? true : false); + if($item instanceof Item and !$target->isBreakable($item) and $ev->getInstaBreak() === false){ + $ev->setCancelled(); + } + if(!$player->isOp() and ($distance = $this->server->getConfigInt("spawn-protection", 16)) > -1){ + $t = new Vector2($target->x, $target->z); + $s = new Vector2($this->getSpawn()->x, $this->getSpawn()->z); + if($t->distance($s) <= $distance){ //set it to cancelled so plugins can bypass this + $ev->setCancelled(); + } + } + $this->server->getPluginManager()->callEvent($ev); + if($ev->isCancelled()){ + return false; + } + }elseif($item instanceof Item and !$target->isBreakable($item)){ + return false; + } + + $target->onBreak($item); + if($item instanceof Item){ + $item->useOn($target); + if($item->isTool() and $item->getMetadata() >= $item->getMaxDurability()){ + $item = Item::get(Item::AIR, 0, 0); + } + } + + return $target->getDrops($item); + } + + /** + * Uses a item on a position and face, placing it or activating the block + * + * @param Vector3 $vector + * @param Item $item + * @param int $face + * @param float $fx default 0.0 + * @param float $fy default 0.0 + * @param float $fz default 0.0 + * @param Player $player default null + * + * @return boolean + */ + public function useItemOn(Vector3 $vector, Item &$item, $face, $fx = 0.0, $fy = 0.0, $fz = 0.0, Player $player = null){ + $target = $this->getBlock($vector); + $block = $target->getSide($face); + + if($block->y > 127 or $block->y < 0){ + return false; + } + + if($target->getID() === Item::AIR){ + return false; + } + + if($player instanceof Player){ + $ev = new PlayerInteractEvent($player, $item, $target, $face); + if(!$player->isOp() and ($distance = $this->server->getConfigInt("spawn-protection", 16)) > -1){ + $t = new Vector2($target->x, $target->z); + $s = new Vector2($this->getSpawn()->x, $this->getSpawn()->z); + if($t->distance($s) <= $distance){ //set it to cancelled so plugins can bypass this + $ev->setCancelled(); + } + } + $this->server->getPluginManager()->callEvent($ev); + if(!$ev->isCancelled()){ + $target->onUpdate(self::BLOCK_UPDATE_TOUCH); + if($target->isActivable === true and $target->onActivate($item, $player) === true){ + return true; + } + } + }elseif($target->isActivable === true and $target->onActivate($item, $player) === true){ + return true; + } + + if($item->isPlaceable()){ + $hand = $item->getBlock(); + $hand->position($block); + }elseif($block->getID() === Item::FIRE){ + $this->setBlock($block, new Air(), true, false, true); + + return false; + }else{ + return false; + } + + if(!($block->isReplaceable === true or ($hand->getID() === Item::SLAB and $block->getID() === Item::SLAB))){ + return false; + } + + if($target->isReplaceable === true){ + $block = $target; + $hand->position($block); + //$face = -1; + } + + //TODO: Implement using Bounding Boxes, all entities + /*if($hand->isSolid === true and $player->inBlock($block)){ + return false; //Entity in block + }*/ + + + if($player instanceof Player){ + $ev = new BlockPlaceEvent($player, $hand, $block, $target, $item); + if(!$player->isOp() and ($distance = $this->server->getConfigInt("spawn-protection", 16)) > -1){ + $t = new Vector2($target->x, $target->z); + $s = new Vector2($this->getSpawn()->x, $this->getSpawn()->z); + if($t->distance($s) <= $distance){ //set it to cancelled so plugins can bypass this + $ev->setCancelled(); + } + } + $this->server->getPluginManager()->callEvent($ev); + if($ev->isCancelled()){ + return false; + } + } + + if($hand->place($item, $block, $target, $face, $fx, $fy, $fz, $player) === false){ + return false; + } + + if($hand->getID() === Item::SIGN_POST or $hand->getID() === Item::WALL_SIGN){ + $tile = new Sign($this, new Compound(false, array( + new String("id", Tile::SIGN), + new Int("x", $block->x), + new Int("y", $block->y), + new Int("z", $block->z), + new String("Text1", ""), + new String("Text2", ""), + new String("Text3", ""), + new String("Text4", "") + ))); + if($player instanceof Player){ + $tile->namedtag->creator = new String("creator", $player->getName()); + } + } + $item->setCount($item->getCount() - 1); + if($item->getCount() <= 0){ + $item = Item::get(Item::AIR, 0, 0); + } + + return true; + } + + /** + * Gets the biome ID of a column + * + * @param int $x + * @param int $z + * + * @return int + */ + public function getBiome($x, $z){ + return $this->level->getBiome((int) $x, (int) $z); + } + + /** + * Sets the biome ID for a column + * + * @param int $x + * @param int $z + * @param int $biome + * + * @return int + */ + public function setBiome($x, $z, $biome){ + return $this->level->getBiome((int) $x, (int) $z, $biome); + } + + /** + * Gets the list of all the entitites in this level + * + * @return Entity[] + */ + public function getEntities(){ + return $this->entities; + } + + /** + * Returns a list of the Tile entities in this level + * + * @return Tile[] + */ + public function getTiles(){ + return $this->tiles; + } + + /** + * Returns a list of the players in this level + * + * @return Player[] + */ + public function getPlayers(){ + return $this->players; + } + + /** + * Returns the Tile in a position, or false if not found + * + * @param Vector3 $pos + * + * @return bool|Tile + */ + public function getTile(Vector3 $pos){ + if($pos instanceof Position and $pos->level !== $this){ + return false; + } + $tiles = $this->getChunkTiles($pos->x >> 4, $pos->z >> 4); + if(count($tiles) > 0){ + foreach($tiles as $tile){ + if($tile->x === (int) $pos->x and $tile->y === (int) $pos->y and $tile->z === (int) $pos->z){ + return $tile; + } + } + } + + return false; + } + + /** + * Gets a raw minichunk + * + * @param int $X + * @param int $Z + * @param int $Y + * + * @return string + */ + public function getMiniChunk($X, $Z, $Y){ + return $this->level->getMiniChunk($X, $Z, $Y); + } + + /** + * Sets a raw minichunk + * + * @param int $X + * @param int $Z + * @param int $Y + * @param string $data (must be 4096 bytes) + * + * @return bool + */ + public function setMiniChunk($X, $Z, $Y, $data){ + $this->changedCount[$X . ":" . $Y . ":" . $Z] = 4096; + if(ADVANCED_CACHE == true){ + Cache::remove("world:{$this->name}:$X:$Z"); + } + + return $this->level->setMiniChunk($X, $Z, $Y, $data); + } + + /** + * Returns a list of the entities on a given chunk + * + * @param int $X + * @param int $Z + * + * @return Entity[] + */ + public function getChunkEntities($X, $Z){ + $index = LevelFormat::getIndex($X, $Z); + if(isset($this->usedChunks[$index]) or $this->loadChunk($X, $Z) === true){ + return $this->chunkEntities[$index]; + } + + return array(); + } + + /** + * Gives a list of the Tile entities on a given chunk + * + * @param int $X + * @param int $Z + * + * @return Tile[] + */ + public function getChunkTiles($X, $Z){ + $index = LevelFormat::getIndex($X, $Z); + if(isset($this->usedChunks[$index]) or $this->loadChunk($X, $Z) === true){ + return $this->chunkTiles[$index]; + } + + return array(); + } + + /** + * Loads a chunk + * + * @param int $X + * @param int $Z + * + * @return bool + */ + public function loadChunk($X, $Z){ + $index = LevelFormat::getIndex($X, $Z); + if(isset($this->usedChunks[$index])){ + return true; + }elseif($this->level->loadChunk($X, $Z) !== false){ + $this->usedChunks[$index] = array(); + $this->chunkTiles[$index] = array(); + $this->chunkEntities[$index] = array(); + $tags = $this->level->getChunkNBT($X, $Z); + if(isset($tags->Entities)){ + foreach($tags->Entities as $nbt){ + if(!isset($nbt["id"])){ + continue; + } + switch($nbt["id"]){ + //TODO: spawn entities + } + } + } + if(isset($tags->TileEntities)){ + foreach($tags->TileEntities as $nbt){ + switch($nbt["id"]){ + case Tile::CHEST: + new Chest($this, $nbt); + break; + case Tile::FURNACE: + new Furnace($this, $nbt); + break; + case Tile::SIGN: + new Sign($this, $nbt); + break; + } + } + } + + return true; + } + + return false; + } + + /** + * Unloads a chunk + * + * @param int $X + * @param int $Z + * @param bool $force + * + * @return bool + */ + public function unloadChunk($X, $Z, $force = false){ + if(!isset($this->level)){ + return false; + } + + if($force !== true and $this->isSpawnChunk($X, $Z)){ + return false; + } + $index = LevelFormat::getIndex($X, $Z); + unset($this->usedChunks[$index]); + unset($this->chunkEntities[$index]); + unset($this->chunkTiles[$index]); + Cache::remove("world:{$this->name}:$X:$Z"); + + return $this->level->unloadChunk($X, $Z, $this->getAutoSave()); + } + + /** + * Returns true if the spawn is part of the spawn + * + * @param int $X + * @param int $Z + * + * @return bool + */ + public function isSpawnChunk($X, $Z){ + $spawnX = $this->level->getData("spawnX") >> 4; + $spawnZ = $this->level->getData("spawnZ") >> 4; + + return abs($X - $spawnX) <= 1 and abs($Z - $spawnZ) <= 1; + } + + /** + * Gets a full chunk or parts of it for networking usage, allows cache usage + * + * @param int $X + * @param int $Z + * @param int $Yndex bitmap of chunks to be returned + * + * @return bool|mixed|string + */ + public function getOrderedChunk($X, $Z, $Yndex){ + if(!isset($this->level)){ + return false; + } + if(ADVANCED_CACHE == true and $Yndex === 0xff){ + $identifier = "world:{$this->name}:" . LevelFormat::getIndex($X, $Z); + if(($cache = Cache::get($identifier)) !== false){ + return $cache; + } + } + + + $raw = array(); + for($Y = 0; $Y < 8; ++$Y){ + if(($Yndex & (1 << $Y)) !== 0){ + $raw[$Y] = $this->level->getMiniChunk($X, $Z, $Y); + } + } + + $ordered = ""; + $flag = chr($Yndex); + for($j = 0; $j < 256; ++$j){ + $ordered .= $flag; + foreach($raw as $mini){ + $ordered .= substr($mini, $j << 5, 24); //16 + 8 + } + } + if(ADVANCED_CACHE == true and $Yndex == 0xff){ + Cache::add($identifier, $ordered, 60); + } + + return $ordered; + } + + /** + * Returns the network minichunk for a given Y + * + * @param int $X + * @param int $Z + * @param int $Y + * + * @return bool|string + */ + public function getOrderedMiniChunk($X, $Z, $Y){ + if(!isset($this->level)){ + return false; + } + $raw = $this->level->getMiniChunk($X, $Z, $Y); + $ordered = ""; + $flag = chr(1 << $Y); + for($j = 0; $j < 256; ++$j){ + $ordered .= $flag . substr($raw, $j << 5, 24); //16 + 8 + } + + return $ordered; + } + + /** + * Returns the raw spawnpoint + * + * @return Position + */ + public function getSpawn(){ + return new Position($this->level->getData("spawnX"), $this->level->getData("spawnY"), $this->level->getData("spawnZ"), $this); + } + + /** + * @param Vector3 $spawn default null + * + * @return bool|Position + */ + public function getSafeSpawn($spawn = null){ + if(!($spawn instanceof Vector3)){ + $spawn = $this->getSpawn(); + } + if($spawn instanceof Vector3){ + $x = (int) round($spawn->x); + $y = (int) round($spawn->y); + $z = (int) round($spawn->z); + for(; $y > 0; --$y){ + $v = new Vector3($x, $y, $z); + $b = $this->getBlock($v->getSide(0)); + if($b === false){ + return $spawn; + }elseif(!($b instanceof Air)){ + break; + } + } + for(; $y < 128; ++$y){ + $v = new Vector3($x, $y, $z); + if($this->getBlock($v->getSide(1)) instanceof Air){ + if($this->getBlock($v) instanceof Air){ + return new Position($x, $y, $z, $this); + } + }else{ + ++$y; + } + } + + return new Position($x, $y, $z, $this); + } + + return false; + } + + /** + * Sets the spawnpoint + * + * @param Vector3 $pos + */ + public function setSpawn(Vector3 $pos){ + $this->level->setData("spawnX", $pos->x); + $this->level->setData("spawnY", $pos->y); + $this->level->setData("spawnZ", $pos->z); + } + + /** + * Gets the current time + * + * @return int + */ + public function getTime(){ + return (int) $this->time; + } + + /** + * Returns the Level name + * + * @return string + */ + public function getName(){ + return $this->name; //return $this->level->getData("name"); + } + + /** + * Sets the current time on the level + * + * @param int $time + */ + public function setTime($time){ + $this->startTime = $this->time = (int) $time; + $this->startCheck = microtime(true); + $this->checkTime(); + } + + /** + * Stops the time for the level, will not save the lock state to disk + */ + public function stopTime(){ + $this->stopTime = true; + $this->startCheck = 0; + $this->checkTime(); + } + + /** + * Start the time again, if it was stopped + */ + public function startTime(){ + $this->stopTime = false; + $this->startCheck = microtime(true); + $this->checkTime(); + } + + /** + * Gets the level seed + * + * @return int + */ + public function getSeed(){ + return (int) $this->level->getData("seed"); + } + + /** + * Sets the seed for the level + * + * @param int $seed + */ + public function setSeed($seed){ + $this->level->setData("seed", (int) $seed); + } +} diff --git a/src/pocketmine/level/format/Chunk.php b/src/pocketmine/level/format/Chunk.php index dd3cbdcfd..a18ca1fe6 100644 --- a/src/pocketmine/level/format/Chunk.php +++ b/src/pocketmine/level/format/Chunk.php @@ -21,6 +21,9 @@ namespace pocketmine\level\format; +use pocketmine\entity\Entity; +use pocketmine\tile\Tile; + interface Chunk{ const SECTION_COUNT = 8; @@ -139,9 +142,22 @@ interface Chunk{ /** * Thread-safe read-only chunk * + * @param bool $includeMaxBlockY + * @param bool $includeBiome + * @param bool $includeBiomeTemp + * * @return ChunkSnapshot */ - public function getChunkSnapshot(); + public function getChunkSnapshot($includeMaxBlockY = true, $includeBiome = false, $includeBiomeTemp = false); + + + public function addEntity(Entity $entity); + + public function removeEntity(Entity $entity); + + public function addTile(Tile $tile); + + public function removeTile(Tile $tile); /** * @return \pocketmine\entity\Entity[] @@ -199,4 +215,9 @@ interface Chunk{ */ public function setSection($fY, ChunkSection $section); + /** + * @return ChunkSection[] + */ + public function getSections(); + } \ No newline at end of file diff --git a/src/pocketmine/level/format/ChunkSection.php b/src/pocketmine/level/format/ChunkSection.php index 0ac13c401..eacdb12cf 100644 --- a/src/pocketmine/level/format/ChunkSection.php +++ b/src/pocketmine/level/format/ChunkSection.php @@ -131,4 +131,12 @@ interface ChunkSection{ */ public function getBlockDataColumn($x, $z); + public function getIdArray(); + + public function getDataArray(); + + public function getSkyLightArray(); + + public function getLightArray(); + } \ No newline at end of file diff --git a/src/pocketmine/level/format/ChunkSnapshot.php b/src/pocketmine/level/format/ChunkSnapshot.php index f31bda41e..9a027c672 100644 --- a/src/pocketmine/level/format/ChunkSnapshot.php +++ b/src/pocketmine/level/format/ChunkSnapshot.php @@ -90,28 +90,4 @@ interface ChunkSnapshot{ */ public function getBiome($x, $z); - /** - * Tests whether a section (mini-chunk) is empty - * - * @param $fY 0-7, (Y / 16) - * - * @return bool - */ - public function isSectionEmpty($fY); - - /** - * @param int $Y 0-7 - * - * @return ChunkSection - */ - public function getSection($Y); - - /** - * @param int $Y 0-7 - * @param ChunkSection $section - * - * @return boolean - */ - public function setSection($Y, ChunkSection $section); - } \ No newline at end of file diff --git a/src/pocketmine/level/format/LevelFormat.php b/src/pocketmine/level/format/LevelProvider.php similarity index 88% rename from src/pocketmine/level/format/LevelFormat.php rename to src/pocketmine/level/format/LevelProvider.php index eb74325f8..d4be8fdfe 100644 --- a/src/pocketmine/level/format/LevelFormat.php +++ b/src/pocketmine/level/format/LevelProvider.php @@ -20,13 +20,10 @@ */ namespace pocketmine\level\format; -use pocketmine\Server; use pocketmine\math\Vector3; +use pocketmine\Server; -/** - * All Level formats must implement this interface - */ -interface LevelFormat{ +interface LevelProvider{ /** * @param Server $server @@ -34,6 +31,9 @@ interface LevelFormat{ */ public function __construct(Server $server, $path); + /** @return string */ + public function getPath(); + /** * Tells if the path is a valid level. * This must tell if the current format supports opening the files in the directory @@ -52,7 +52,7 @@ interface LevelFormat{ * @param int $Z absolute Chunk Z value * @param bool $create Whether to generate the chunk if it does not exist * - * @return ChunkSnapshot + * @return Chunk */ public function getChunk($X, $Z, $create = false); @@ -67,17 +67,22 @@ interface LevelFormat{ public function unloadChunk($X, $Z); - public function isChunkLoaded($X, $Z); + public function isChunkGenerated($X, $Z); + + public function getName(); /** * @return Vector3 */ public function getSpawn(); - public function getName(); + /** + * @param Vector3 $pos + */ + public function setSpawn(Vector3 $pos); /** - * @return ChunkSnapshot + * @return Chunk */ public function getLoadedChunks(); diff --git a/src/pocketmine/level/format/anvil/Anvil.php b/src/pocketmine/level/format/anvil/Anvil.php index d74e60440..b7ce9d6a1 100644 --- a/src/pocketmine/level/format/anvil/Anvil.php +++ b/src/pocketmine/level/format/anvil/Anvil.php @@ -21,9 +21,9 @@ namespace pocketmine\level\format\anvil; -use pocketmine\level\format\LevelFormat; +use pocketmine\level\format\generic\BaseLevelProvider; -class Anvil implements LevelFormat{ +class Anvil extends BaseLevelProvider{ protected $basePath; public function __construct($path, $levelName){ diff --git a/src/pocketmine/level/format/anvil/Chunk.php b/src/pocketmine/level/format/anvil/Chunk.php index 114ca1a05..2a87f2ef4 100644 --- a/src/pocketmine/level/format/anvil/Chunk.php +++ b/src/pocketmine/level/format/anvil/Chunk.php @@ -22,6 +22,7 @@ namespace pocketmine\level\format\anvil; use pocketmine\level\format\generic\BaseChunk; +use pocketmine\level\format\generic\EmptyChunkSection; use pocketmine\level\Level; use pocketmine\nbt\NBT; use pocketmine\nbt\tag\Compound; @@ -72,4 +73,34 @@ class Chunk extends BaseChunk{ parent::__construct($level, $this->nbt["xPos"], $this->nbt["zPos"], $sections); } + + public function getChunkSnapshot($includeMaxBlockY = true, $includeBiome = false, $includeBiomeTemp = false){ + $blockId = ""; + $blockData = ""; + $blockSkyLight = ""; + $blockLight = ""; + $emptySections = [false, false, false, false, false, false, false, false]; + + $emptyBlocks = str_repeat("\x00", 4096); + $emptyHalf = str_repeat("\x00", 2048); + + foreach($this->sections as $i => $section){ + if($section instanceof EmptyChunkSection){ + $blockId .= $emptyBlocks; + $blockData .= $emptyHalf; + $blockSkyLight .= $emptyHalf; + $blockLight .= $emptyHalf; + $emptySections[$i] = true; + }else{ + $blockId .= $section->getIdArray(); + $blockData .= $section->getDataArray(); + $blockSkyLight .= $section->getSkyLightArray(); + $blockLight .= $section->getLightArray(); + } + } + + //TODO: maxBlockY, biomeMap, biomeTemp + + return new ChunkSnapshot($this->getX(), $this->getZ(), $this->getLevel()->getName(), $this->getLevel()->getTime(), $blockId, $blockData, $blockSkyLight, $blockLight, $emptySections, null, null, null, null); + } } \ No newline at end of file diff --git a/src/pocketmine/level/format/anvil/ChunkSection.php b/src/pocketmine/level/format/anvil/ChunkSection.php index 2241782f6..ecde6a893 100644 --- a/src/pocketmine/level/format/anvil/ChunkSection.php +++ b/src/pocketmine/level/format/anvil/ChunkSection.php @@ -150,4 +150,20 @@ class ChunkSection implements \pocketmine\level\format\ChunkSection{ return $column; } + public function getIdArray(){ + return $this->blocks; + } + + public function getDataArray(){ + return $this->data; + } + + public function getSkyLightArray(){ + return $this->skyLight; + } + + public function getLightArray(){ + return $this->blockLight; + } + } \ No newline at end of file diff --git a/src/pocketmine/level/format/anvil/ChunkSnapshot.php b/src/pocketmine/level/format/anvil/ChunkSnapshot.php new file mode 100644 index 000000000..279fb2ade --- /dev/null +++ b/src/pocketmine/level/format/anvil/ChunkSnapshot.php @@ -0,0 +1,78 @@ +blockId{ + (($y >> 4) << 12) //get section index + + ($y << 8) + ($z << 4) + $x //get block index in section + }); + } + + public function getBlockData($x, $y, $z){ + $data = ord($this->blockData{ + (($y >> 4) << 11) //get section index + + ($y << 7) + ($z << 3) + ($x >> 1) //get block index in section + }); + if(($y & 1) === 0){ + return $data & 0x0F; + }else{ + return $data >> 4; + } + } + + public function getBlockSkyLight($x, $y, $z){ + $level = ord($this->skyLight{ + (($y >> 4) << 11) //get section index + + ($y << 7) + ($z << 3) + ($x >> 1) //get block index in section + }); + if(($y & 1) === 0){ + return $level & 0x0F; + }else{ + return $level >> 4; + } + } + + public function getBlockLight($x, $y, $z){ + $level = ord($this->light{ + (($y >> 4) << 11) //get section index + + ($y << 7) + ($z << 3) + ($x >> 1) //get block index in section + }); + if(($y & 1) === 0){ + return $level & 0x0F; + }else{ + return $level >> 4; + } + } + + public function getBiome(){ + return 0; //TODO + } + + public function getHighestBlockAt($x, $z){ + return 127; //TODO + } +} \ No newline at end of file diff --git a/src/pocketmine/level/format/generic/BaseChunk.php b/src/pocketmine/level/format/generic/BaseChunk.php index f5cf50788..ec839285c 100644 --- a/src/pocketmine/level/format/generic/BaseChunk.php +++ b/src/pocketmine/level/format/generic/BaseChunk.php @@ -71,13 +71,29 @@ abstract class BaseChunk implements Chunk{ } public function getBlock($x, $y, $z, &$blockId, &$meta = null){ - $this->sections[$y >> 4]->getBlock($x, $y - ($y >> 4), $z, $blockId, $meta); + return $this->sections[$y >> 4]->getBlock($x, $y - ($y >> 4), $z, $blockId, $meta); } public function setBlock($x, $y, $z, $blockId = null, $meta = null){ $this->sections[$y >> 4]->setBlock($x, $y - ($y >> 4), $z, $blockId, $meta); } + public function getBlockId($x, $y, $z){ + return $this->sections[$y >> 4]->getBlockId($x, $y - ($y >> 4), $z); + } + + public function setBlockId($x, $y, $z, $id){ + $this->sections[$y >> 4]->setBlockId($x, $y - ($y >> 4), $z, $id); + } + + public function getBlockData($x, $y, $z){ + return $this->sections[$y >> 4]->getBlockData($x, $y - ($y >> 4), $z); + } + + public function setBlockData($x, $y, $z, $data){ + $this->sections[$y >> 4]->setBlockData($x, $y - ($y >> 4), $z, $data); + } + public function getBlockSkyLight($x, $y, $z){ return $this->sections[$y >> 4]->getBlockSkyLight($x, $y - ($y >> 4), $z); } @@ -120,4 +136,9 @@ abstract class BaseChunk implements Chunk{ public function setSection($fY, ChunkSection $section){ $this->sections[(int) $fY] = $section; } + + public function getSections(){ + return $this->sections; + } + } \ No newline at end of file diff --git a/src/pocketmine/level/format/generic/BaseChunkSnapshot.php b/src/pocketmine/level/format/generic/BaseChunkSnapshot.php new file mode 100644 index 000000000..4d08b4f2d --- /dev/null +++ b/src/pocketmine/level/format/generic/BaseChunkSnapshot.php @@ -0,0 +1,64 @@ +x = $x; + $this->z = $z; + $this->levelName = $levelName; + $this->levelTime = $levelTime; + $this->blockId = $blockId; + $this->blockData = $blockData; + $this->skyLight = $skyLight; + $this->light = $light; + } + + public function getX(){ + return $this->x; + } + + public function getZ(){ + return $this->z; + } + + public function getLevelName(){ + return $this->levelName; + } + + public function getLevelTime(){ + return $this->levelTime; + } +} \ No newline at end of file diff --git a/src/pocketmine/level/format/generic/BaseLevelProvider.php b/src/pocketmine/level/format/generic/BaseLevelProvider.php new file mode 100644 index 000000000..87245615a --- /dev/null +++ b/src/pocketmine/level/format/generic/BaseLevelProvider.php @@ -0,0 +1,36 @@ +server = $server; + $this->path = $path; + } +} \ No newline at end of file diff --git a/src/pocketmine/level/format/generic/EmptyChunkSection.php b/src/pocketmine/level/format/generic/EmptyChunkSection.php index c99c3df6f..dfba5b0ee 100644 --- a/src/pocketmine/level/format/generic/EmptyChunkSection.php +++ b/src/pocketmine/level/format/generic/EmptyChunkSection.php @@ -38,6 +38,22 @@ class EmptyChunkSection implements ChunkSection{ return "\x00\x00\x00\x00\x00\x00\x00\x00"; } + public function getIdArray(){ + return str_repeat("\x00", 4096); + } + + public function getDataArray(){ + return str_repeat("\x00", 2048); + } + + public function getSkyLightArray(){ + return str_repeat("\x00", 2048); + } + + public function getLightArray(){ + return str_repeat("\x00", 2048); + } + final public function setBlockId($x, $y, $z, $id){ } From 2566f2c4cb2ddf39063a499bdcca81197adcd3d9 Mon Sep 17 00:00:00 2001 From: Shoghi Cervantes Date: Sat, 7 Jun 2014 22:29:45 +0200 Subject: [PATCH 2/3] Worked on chunk loading --- src/pocketmine/Player.php | 28 ++-- src/pocketmine/level/Level.php | 127 +++++++++--------- src/pocketmine/level/WorldGenerator.php | 4 +- src/pocketmine/level/generator/object/Ore.php | 2 +- 4 files changed, 87 insertions(+), 74 deletions(-) diff --git a/src/pocketmine/Player.php b/src/pocketmine/Player.php index 55a5da820..db6a30201 100644 --- a/src/pocketmine/Player.php +++ b/src/pocketmine/Player.php @@ -612,20 +612,25 @@ class Player extends Human implements CommandSender, InventoryHolder, IPlayer{ $index = key($this->chunksOrder); LevelFormat::getXZ($index, $X, $Z); - $this->getLevel()->loadChunk($X, $Z); - if(!$this->getLevel()->isChunkPopulated($X, $Z)){ - $this->getLevel()->loadChunk($X - 1, $Z); - $this->getLevel()->loadChunk($X + 1, $Z); - $this->getLevel()->loadChunk($X, $Z - 1); - $this->getLevel()->loadChunk($X, $Z + 1); - $this->getLevel()->loadChunk($X + 1, $Z + 1); - $this->getLevel()->loadChunk($X + 1, $Z - 1); - $this->getLevel()->loadChunk($X - 1, $Z - 1); - $this->getLevel()->loadChunk($X - 1, $Z + 1); + $radius = 1; + for($z = $Z - $radius; $z <= ($Z + $radius); ++$z){ + for($x = $X - $radius; $x <= ($X + $radius); ++$x){ + $this->getLevel()->loadChunk($x, $z); + if(!$this->getLevel()->isChunkPopulated($x, $z)){ + $this->getLevel()->loadChunk($x - 1, $z); + $this->getLevel()->loadChunk($x + 1, $z); + $this->getLevel()->loadChunk($x, $z - 1); + $this->getLevel()->loadChunk($x, $z + 1); + $this->getLevel()->loadChunk($x + 1, $z + 1); + $this->getLevel()->loadChunk($x + 1, $z - 1); + $this->getLevel()->loadChunk($x - 1, $z - 1); + $this->getLevel()->loadChunk($x - 1, $z + 1); + } + } } foreach($lastChunk as $index => $Yndex){ - if($Yndex !== 0xff){ + if($Yndex === 0){ $X = null; $Z = null; LevelFormat::getXZ($index, $X, $Z); @@ -1153,6 +1158,7 @@ class Player extends Human implements CommandSender, InventoryHolder, IPlayer{ $this->server->getPluginManager()->callEvent(new PlayerJoinEvent($this, $this->username . " joined the game")); + $this->orderChunks(); //Get first chunk ready break; case ProtocolInfo::READY_PACKET: if($this->loggedIn === false){ diff --git a/src/pocketmine/level/Level.php b/src/pocketmine/level/Level.php index 2cf59fe2b..4cdf51066 100644 --- a/src/pocketmine/level/Level.php +++ b/src/pocketmine/level/Level.php @@ -95,6 +95,7 @@ class Level{ /** @var Server */ private $server; private $name; + /** @var Player[][] */ private $usedChunks; private $changedBlocks; private $changedCount; @@ -302,16 +303,18 @@ class Level{ if(count($this->changedBlocks) > 0){ foreach($this->changedBlocks as $index => $mini){ - foreach($mini as $blocks){ - /** @var Block $b */ - foreach($blocks as $b){ - $pk = new UpdateBlockPacket; - $pk->x = $b->x; - $pk->y = $b->y; - $pk->z = $b->z; - $pk->block = $b->getID(); - $pk->meta = $b->getDamage(); - $this->server->broadcastPacket($this->players, $pk); + if(isset($this->usedChunks[$index]) and count($this->usedChunks[$index]) > 0){ + foreach($mini as $blocks){ + /** @var Block $b */ + foreach($blocks as $b){ + $pk = new UpdateBlockPacket; + $pk->x = $b->x; + $pk->y = $b->y; + $pk->z = $b->z; + $pk->block = $b->getID(); + $pk->meta = $b->getDamage(); + $this->server->broadcastPacket($this->usedChunks[$index], $pk); + } } } } @@ -383,7 +386,6 @@ class Level{ public function populateChunk($X, $Z){ $this->level->setPopulated($X, $Z); $this->generator->populateChunk($X, $Z); - return true; } @@ -471,7 +473,7 @@ class Level{ * @param int $type */ public function updateAround(Vector3 $pos, $type = self::BLOCK_UPDATE_NORMAL){ - $block = $this->getBlockRaw($pos); + $block = $this->getBlock($pos); $block->getSide(0)->onUpdate($type); $block->getSide(1)->onUpdate($type); $block->getSide(2)->onUpdate($type); @@ -583,33 +585,35 @@ class Level{ */ public function setBlockRaw(Vector3 $pos, Block $block, $direct = true, $send = true){ if(($ret = $this->level->setBlock($pos->x, $pos->y, $pos->z, $block->getID(), $block->getDamage())) === true and $send !== false){ - if($direct === true){ - $pk = new UpdateBlockPacket; - $pk->x = $pos->x; - $pk->y = $pos->y; - $pk->z = $pos->z; - $pk->block = $block->getID(); - $pk->meta = $block->getDamage(); - $this->server->broadcastPacket($this->players, $pk); - }elseif($direct === false){ - if(!($pos instanceof Position)){ - $pos = new Position($pos->x, $pos->y, $pos->z, $this); + $index = LevelFormat::getIndex($pos->x >> 4, $pos->z >> 4); + if(isset($this->usedChunks[$index]) and count($this->usedChunks[$index]) > 0){ + if($direct === true){ + $pk = new UpdateBlockPacket; + $pk->x = $pos->x; + $pk->y = $pos->y; + $pk->z = $pos->z; + $pk->block = $block->getID(); + $pk->meta = $block->getDamage(); + $this->server->broadcastPacket($this->usedChunks[$index], $pk); + }elseif($direct === false){ + if(!($pos instanceof Position)){ + $pos = new Position($pos->x, $pos->y, $pos->z, $this); + } + $block->position($pos); + if(ADVANCED_CACHE == true){ + Cache::remove("world:{$this->name}:{$index}"); + } + if(!isset($this->changedBlocks[$index])){ + $this->changedBlocks[$index] = []; + $this->changedCount[$index] = 0; + } + $Y = $pos->y >> 4; + if(!isset($this->changedBlocks[$index][$Y])){ + $this->changedBlocks[$index][$Y] = []; + $this->changedCount[$index] |= 1 << $Y; + } + $this->changedBlocks[$index][$Y][] = clone $block; } - $block->position($pos); - $index = LevelFormat::getIndex($pos->x >> 4, $pos->z >> 4); - if(ADVANCED_CACHE == true){ - Cache::remove("world:{$this->name}:{$index}"); - } - if(!isset($this->changedBlocks[$index])){ - $this->changedBlocks[$index] = []; - $this->changedCount[$index] = 0; - } - $Y = $pos->y >> 4; - if(!isset($this->changedBlocks[$index][$Y])){ - $this->changedBlocks[$index][$Y] = []; - $this->changedCount[$index] |= 1 << $Y; - } - $this->changedBlocks[$index][$Y][] = clone $block; } } @@ -637,29 +641,32 @@ class Level{ } $block->position($pos); - if($direct === true){ - $pk = new UpdateBlockPacket; - $pk->x = $pos->x; - $pk->y = $pos->y; - $pk->z = $pos->z; - $pk->block = $block->getID(); - $pk->meta = $block->getDamage(); - $this->server->broadcastPacket($this->players, $pk); - }else{ - $index = LevelFormat::getIndex($pos->x >> 4, $pos->z >> 4); - if(ADVANCED_CACHE == true){ - Cache::remove("world:{$this->name}:{$index}"); + $index = LevelFormat::getIndex($pos->x >> 4, $pos->z >> 4); + if(isset($this->usedChunks[$index]) and count($this->usedChunks[$index]) > 0){ + if($direct === true){ + $pk = new UpdateBlockPacket; + $pk->x = $pos->x; + $pk->y = $pos->y; + $pk->z = $pos->z; + $pk->block = $block->getID(); + $pk->meta = $block->getDamage(); + $this->server->broadcastPacket($this->usedChunks[$index], $pk); + }else{ + + if(ADVANCED_CACHE == true){ + Cache::remove("world:{$this->name}:{$index}"); + } + if(!isset($this->changedBlocks[$index])){ + $this->changedBlocks[$index] = []; + $this->changedCount[$index] = 0; + } + $Y = $pos->y >> 4; + if(!isset($this->changedBlocks[$index][$Y])){ + $this->changedBlocks[$index][$Y] = []; + $this->changedCount[$index] |= 1 << $Y; + } + $this->changedBlocks[$index][$Y][] = clone $block; } - if(!isset($this->changedBlocks[$index])){ - $this->changedBlocks[$index] = []; - $this->changedCount[$index] = 0; - } - $Y = $pos->y >> 4; - if(!isset($this->changedBlocks[$index][$Y])){ - $this->changedBlocks[$index][$Y] = []; - $this->changedCount[$index] |= 1 << $Y; - } - $this->changedBlocks[$index][$Y][] = clone $block; } if($update === true){ diff --git a/src/pocketmine/level/WorldGenerator.php b/src/pocketmine/level/WorldGenerator.php index 4d62dbd24..6a051cd72 100644 --- a/src/pocketmine/level/WorldGenerator.php +++ b/src/pocketmine/level/WorldGenerator.php @@ -61,8 +61,8 @@ class WorldGenerator{ public function generate(){ $this->generator->init($this->level, $this->random); - for($Z = 7; $Z <= 9; ++$Z){ - for($X = 7; $X <= 9; ++$X){ + for($Z = 6; $Z <= 10; ++$Z){ + for($X = 6; $X <= 10; ++$X){ $this->level->level->loadChunk($X, $Z); } } diff --git a/src/pocketmine/level/generator/object/Ore.php b/src/pocketmine/level/generator/object/Ore.php index efdc10ddc..6808e85f2 100644 --- a/src/pocketmine/level/generator/object/Ore.php +++ b/src/pocketmine/level/generator/object/Ore.php @@ -40,7 +40,7 @@ class Ore{ } public function canPlaceObject(Level $level, $x, $y, $z){ - return ($level->level->getBlockID($x, $y, $z) !== 0); + return ($level->level->getBlockID($x, $y, $z) === 1); } public function placeObject(Level $level, Vector3 $pos){ From 115b4cf4ac0495f9410c9164283b668016af4640 Mon Sep 17 00:00:00 2001 From: Shoghi Cervantes Date: Mon, 9 Jun 2014 11:35:52 +0200 Subject: [PATCH 3/3] Updated Levels :D --- src/pocketmine/entity/Entity.php | 39 ++- src/pocketmine/level/ChunkManager.php | 66 +++++ src/pocketmine/level/Level.php | 63 ++--- src/pocketmine/level/Level_OLD.php | 63 ----- src/pocketmine/level/format/Chunk.php | 3 +- src/pocketmine/level/format/ChunkSection.php | 5 + src/pocketmine/level/format/LevelProvider.php | 62 ++++- src/pocketmine/level/format/SimpleChunk.php | 242 +++++++++++++++++ src/pocketmine/level/format/anvil/Anvil.php | 112 +++++++- src/pocketmine/level/format/anvil/Chunk.php | 15 +- .../level/format/anvil/ChunkSection.php | 4 + .../level/format/anvil/RegionLoader.php | 71 ++++- .../level/format/generic/BaseChunk.php | 121 ++++++++- .../format/generic/BaseLevelProvider.php | 77 +++++- src/pocketmine/level/generator/Flat.php | 41 ++- .../generator/GenerationChunkManager.php | 187 +++++++++++++ .../level/generator/GenerationManager.php | 257 ++++++++++++++++++ .../level/generator/GenerationThread.php | 96 +++++++ src/pocketmine/level/generator/Generator.php | 2 +- src/pocketmine/level/generator/Normal.php | 6 +- .../level/generator/object/BigTree.php | 5 +- src/pocketmine/level/generator/object/Ore.php | 23 +- .../level/generator/object/PineTree.php | 16 +- .../level/generator/object/Pond.php | 5 +- .../level/generator/object/SmallTree.php | 21 +- .../level/generator/object/SpruceTree.php | 18 +- .../level/generator/object/TallGrass.php | 24 +- .../level/generator/object/Tree.php | 7 +- .../level/generator/populator/Mineshaft.php | 3 +- .../level/generator/populator/Ore.php | 5 +- .../level/generator/populator/Pond.php | 15 +- .../level/generator/populator/Populator.php | 3 +- .../level/generator/populator/TallGrass.php | 20 +- .../level/generator/populator/Tree.php | 10 +- src/pocketmine/tile/Chest.php | 5 +- src/pocketmine/tile/Container.php | 13 + src/pocketmine/tile/Furnace.php | 5 +- src/pocketmine/tile/Sign.php | 6 +- src/pocketmine/tile/Spawnable.php | 5 +- src/pocketmine/tile/Tile.php | 19 +- src/raklib | 2 +- 41 files changed, 1492 insertions(+), 270 deletions(-) create mode 100644 src/pocketmine/level/ChunkManager.php create mode 100644 src/pocketmine/level/format/SimpleChunk.php create mode 100644 src/pocketmine/level/generator/GenerationChunkManager.php create mode 100644 src/pocketmine/level/generator/GenerationManager.php create mode 100644 src/pocketmine/level/generator/GenerationThread.php diff --git a/src/pocketmine/entity/Entity.php b/src/pocketmine/entity/Entity.php index acc55c374..3cd22d47b 100644 --- a/src/pocketmine/entity/Entity.php +++ b/src/pocketmine/entity/Entity.php @@ -31,6 +31,7 @@ use pocketmine\event\entity\EntityMotionEvent; use pocketmine\event\entity\EntityMoveEvent; use pocketmine\event\entity\EntitySpawnEvent; use pocketmine\event\entity\EntityTeleportEvent; +use pocketmine\level\format\Chunk; use pocketmine\level\format\pmf\LevelFormat; use pocketmine\level\Level; use pocketmine\level\Position; @@ -75,11 +76,8 @@ abstract class Entity extends Position implements Metadatable{ /** @var int */ public $chunkZ; - /** - * TODO: REMOVE - * @var int - */ - public $chunkIndex; + /** @var Chunk */ + public $chunk; public $lastX; public $lastY; @@ -141,12 +139,13 @@ abstract class Entity extends Position implements Metadatable{ public $closed = false; - public function __construct(Level $level, Compound $nbt){ + public function __construct(Chunk $chunk, Compound $nbt){ $this->id = Entity::$entityCount++; $this->justCreated = true; $this->namedtag = $nbt; - $this->setLevel($level, true); //Create a hard reference - $this->server = Server::getInstance(); + $this->chunk = $chunk; + $this->setLevel($chunk->getLevel()->getLevel(), true); //Create a hard reference + $this->server = $chunk->getLevel()->getLevel()->getServer(); $this->boundingBox = new AxisAlignedBB(0, 0, 0, 0, 0, 0); $this->setPositionAndRotation(new Vector3( @@ -187,11 +186,9 @@ abstract class Entity extends Position implements Metadatable{ } $this->invulnerable = $this->namedtag["Invulnerable"] > 0 ? true : false; - $index = LevelFormat::getIndex($this->x >> 4, $this->z >> 4); - $this->chunkIndex = $index; + $this->chunk->addEntity($this); $this->getLevel()->addEntity($this); $this->initEntity(); - $this->getLevel()->chunkEntities[$this->chunkIndex][$this->id] = $this; $this->lastUpdate = $this->spawnTime = microtime(true); $this->justCreated = false; $this->server->getPluginManager()->callEvent(new EntitySpawnEvent($this)); @@ -228,7 +225,7 @@ abstract class Entity extends Position implements Metadatable{ * @param Player $player */ public function spawnTo(Player $player){ - if(!isset($this->hasSpawned[$player->getID()]) and $player->chunksLoaded[$this->chunkIndex] !== 0xff){ + if(!isset($this->hasSpawned[$player->getID()]) and $player->chunksLoaded[Level::chunkHash($this->chunk->getX(), $this->chunk->getZ())] !== 0xff){ $this->hasSpawned[$player->getID()] = $player; } } @@ -585,7 +582,7 @@ abstract class Entity extends Position implements Metadatable{ } $this->getLevel()->removeEntity($this); - unset($this->getLevel()->chunkEntities[$this->chunkIndex][$this->id]); + $this->chunk->removeEntity($this); $this->despawnFromAll(); if($this instanceof Player){ foreach($this->chunksLoaded as $index => $Yndex){ @@ -611,7 +608,7 @@ abstract class Entity extends Position implements Metadatable{ $this->dataPacket($pk); } $this->spawnToAll(); - $this->chunkIndex = false; + $this->chunk = null; } public function getPosition(){ @@ -856,12 +853,12 @@ abstract class Entity extends Position implements Metadatable{ $this->boundingBox->setBounds($pos->x - $radius, $pos->y, $pos->z - $radius, $pos->x + $radius, $pos->y + $this->height, $pos->z + $radius); - if(($index = LevelFormat::getIndex($this->x >> 4, $this->z >> 4)) !== $this->chunkIndex){ - if($this->chunkIndex !== false){ - unset($this->getLevel()->chunkEntities[$this->chunkIndex][$this->id]); + if($this->chunk === null or ($this->chunk->getX() !== ($this->x >> 4) and $this->chunk->getZ() !== ($this->z >> 4))){ + if($this->chunk instanceof Chunk){ + $this->chunk->removeEntity($this); } - $this->chunkIndex = $index; $this->getLevel()->loadChunk($this->x >> 4, $this->z >> 4); + $this->chunk = $this->getLevel()->getChunkAt($this->x >> 4, $this->z >> 4); if(!$this->justCreated){ $newChunk = $this->getLevel()->getUsingChunk($this->x >> 4, $this->z >> 4); @@ -877,7 +874,7 @@ abstract class Entity extends Position implements Metadatable{ } } - $this->getLevel()->chunkEntities[$this->chunkIndex][$this->id] = $this; + $this->chunk->addEntity($this); } $this->scheduleUpdate(); @@ -970,8 +967,10 @@ abstract class Entity extends Position implements Metadatable{ if($this->closed === false){ $this->closed = true; unset(Entity::$needUpdate[$this->id]); + if($this->chunk instanceof Chunk){ + $this->chunk->removeEntity($this); + } $this->getLevel()->removeEntity($this); - unset($this->getLevel()->chunkEntities[$this->chunkIndex][$this->id]); $this->despawnFromAll(); $this->server->getPluginManager()->callEvent(new EntityDespawnEvent($this)); } diff --git a/src/pocketmine/level/ChunkManager.php b/src/pocketmine/level/ChunkManager.php new file mode 100644 index 000000000..f1ada3547 --- /dev/null +++ b/src/pocketmine/level/ChunkManager.php @@ -0,0 +1,66 @@ +levelId = static::$levelIdCounter++; $this->server = $server; - $this->provider = $provider; + if(is_subclass_of($provider, "pocketmine\\level\\format\\LevelProvider", true)){ + $this->provider = new $provider($this, $path); + }else{ + throw new \Exception("Provider is not a subclass of LevelProvider"); + } $this->players = new \SplObjectStorage(); $this->entities = new \SplObjectStorage(); $this->tiles = new \SplObjectStorage(); } + /** + * @return Server + */ + public function getServer(){ + return $this->server; + } + /** * @return LevelProvider */ @@ -136,35 +146,6 @@ class Level implements Metadatable{ return Block::get($blockId, $meta, Position::fromObject(clone $pos, $this)); } - public function getCollisionBlocks(AxisAlignedBB $bb){ - $minX = floor($bb->minX); - $minY = floor($bb->minY); - $minZ = floor($bb->minZ); - $maxX = floor($bb->maxX + 1); - $maxY = floor($bb->maxY + 1); - $maxZ = floor($bb->maxZ + 1); - - $collides = []; - - for($z = $minZ; $z < $maxZ; ++$z){ - for($x = $minX; $x < $maxX; ++$x){ - if($this->isChunkLoaded($x >> 4, $z >> 4)){ - for($y = $minY - 1; $y < $maxY; ++$y){ - $this->getBlock(new Vector3($x, $y, $z))->collidesWithBB($bb, $collides); - } - } - } - } - - return $collides; - } - - public function isFullBlock(Vector3 $pos){ - $bb = $this->getBlock($pos)->getBoundingBox(); - - return $bb instanceof AxisAlignedBB and $bb->getAverageEdgeLength() >= 1; - } - /** * Sets on Vector3 the data from a Block object, * does block updates and puts the changes to the send queue. @@ -173,7 +154,8 @@ class Level implements Metadatable{ * @param Block $block */ public function setBlock(Vector3 $pos, Block $block){ - //TODO: handle block setting + $this->getChunkAt($pos->x >> 4, $pos->z >> 4)->setBlock($pos->x & 0x0f, $pos->y & 0x7f, $pos->z & 0x0f, $block->getID(), $block->getDamage()); + //TODO: //block updates //block change send queue //etc. @@ -288,7 +270,7 @@ class Level implements Metadatable{ * * @return Chunk */ - protected function getChunkAt($x, $z, $create = false){ + public function getChunkAt($x, $z, $create = false){ $this->provider->getChunk($x, $z, $create); } @@ -314,8 +296,7 @@ class Level implements Metadatable{ * @return bool */ public function isChunkLoaded($x, $z){ - //TODO - return false; + return $this->provider->isChunkLoaded($x, $z); } /** diff --git a/src/pocketmine/level/Level_OLD.php b/src/pocketmine/level/Level_OLD.php index 4cdf51066..cc7597908 100644 --- a/src/pocketmine/level/Level_OLD.php +++ b/src/pocketmine/level/Level_OLD.php @@ -1140,69 +1140,6 @@ class Level{ return $this->level->isChunkLoaded($X, $Z); } - /** - * Loads a chunk - * - * @param int $X - * @param int $Z - * - * @return bool - */ - public function loadChunk($X, $Z){ - $index = LevelFormat::getIndex($X, $Z); - if(isset($this->usedChunks[$index])){ - return true; - }elseif($this->level->loadChunk($X, $Z) !== false){ - $this->usedChunks[$index] = []; - if(!isset($this->chunkTiles[$index])){ - $this->chunkTiles[$index] = []; - } - if(!isset($this->chunkEntities[$index])){ - $this->chunkEntities[$index] = []; - } - $tags = $this->level->getChunkNBT($X, $Z); - if(isset($tags->Entities)){ - foreach($tags->Entities as $nbt){ - if(!isset($nbt->id)){ - continue; - } - - if($nbt->id instanceof String){ //New format - switch($nbt["id"]){ - case "Item": - (new DroppedItem($this, $nbt))->spawnToAll(); - break; - } - }else{ //Old format - - } - } - } - if(isset($tags->TileEntities)){ - foreach($tags->TileEntities as $nbt){ - if(!isset($nbt->id)){ - continue; - } - switch($nbt["id"]){ - case Tile::CHEST: - new Chest($this, $nbt); - break; - case Tile::FURNACE: - new Furnace($this, $nbt); - break; - case Tile::SIGN: - new Sign($this, $nbt); - break; - } - } - } - - return true; - } - - return false; - } - /** * Unloads a chunk * diff --git a/src/pocketmine/level/format/Chunk.php b/src/pocketmine/level/format/Chunk.php index 2502bbf88..4881cd2d6 100644 --- a/src/pocketmine/level/format/Chunk.php +++ b/src/pocketmine/level/format/Chunk.php @@ -38,7 +38,7 @@ interface Chunk{ public function getZ(); /** - * @return \pocketmine\level\Level + * @return \pocketmine\level\format\LevelProvider */ public function getLevel(); @@ -60,6 +60,7 @@ interface Chunk{ * @param int $z 0-15 * @param int $blockId , if null, do not change * @param int $meta 0-15, if null, do not change + * */ public function setBlock($x, $y, $z, $blockId = null, $meta = null); diff --git a/src/pocketmine/level/format/ChunkSection.php b/src/pocketmine/level/format/ChunkSection.php index ff81872fa..c2fe54e01 100644 --- a/src/pocketmine/level/format/ChunkSection.php +++ b/src/pocketmine/level/format/ChunkSection.php @@ -23,6 +23,11 @@ namespace pocketmine\level\format; interface ChunkSection{ + /** + * @return int + */ + public function getY(); + /** * @param int $x 0-15 * @param int $y 0-15 diff --git a/src/pocketmine/level/format/LevelProvider.php b/src/pocketmine/level/format/LevelProvider.php index 0793272c2..a3f5875d6 100644 --- a/src/pocketmine/level/format/LevelProvider.php +++ b/src/pocketmine/level/format/LevelProvider.php @@ -21,16 +21,16 @@ namespace pocketmine\level\format; +use pocketmine\level\Level; use pocketmine\math\Vector3; -use pocketmine\Server; interface LevelProvider{ /** - * @param Server $server + * @param Level $level * @param string $path */ - public function __construct(Server $server, $path); + public function __construct(Level $level, $path); /** @return string */ public function getPath(); @@ -57,21 +57,64 @@ interface LevelProvider{ */ public function getChunk($X, $Z, $create = false); - /** - * @return bool - */ public function saveChunks(); + /** + * @param int $X + * @param int $Z + */ + public function saveChunk($X, $Z); + public function unloadChunks(); + /** + * @param int $X + * @param int $Z + * + * @return bool + */ public function loadChunk($X, $Z); - public function unloadChunk($X, $Z); + /** + * @param int $X + * @param int $Z + * @param bool $safe + * + * @return bool + */ + public function unloadChunk($X, $Z, $safe = true); + /** + * @param int $X + * @param int $Z + * + * @return bool + */ public function isChunkGenerated($X, $Z); + /** + * @param int $X + * @param int $Z + * + * @return bool + */ + public function isChunkLoaded($X, $Z); + + /** + * @return string + */ public function getName(); + /** + * @return int + */ + public function getTime(); + + /** + * @param int $value + */ + public function setTime($value); + /** * @return Vector3 */ @@ -87,4 +130,9 @@ interface LevelProvider{ */ public function getLoadedChunks(); + /** + * @return Level + */ + public function getLevel(); + } \ No newline at end of file diff --git a/src/pocketmine/level/format/SimpleChunk.php b/src/pocketmine/level/format/SimpleChunk.php new file mode 100644 index 000000000..c2a4ed1c3 --- /dev/null +++ b/src/pocketmine/level/format/SimpleChunk.php @@ -0,0 +1,242 @@ +x = $chunkX; + $this->z = $chunkZ; + $this->flags = $flags; + for($y = 0; $y < self::$HEIGHT; ++$y){ + $this->ids[$y] = isset($ids[$y]) ? $ids[$y] : str_repeat("\x00", 4096); + $this->meta[$y] = isset($meta[$y]) ? $meta[$y] : str_repeat("\x00", 2048); + } + } + + /** + * @return int + */ + public function getX(){ + return $this->x; + } + + /** + * @return int + */ + public function getZ(){ + return $this->z; + } + + /** + * @param int $x + */ + public function setX($x){ + $this->x = $x; + } + + /** + * @param int $z + */ + public function setZ($z){ + $this->z = $z; + } + + /** + * @return bool + */ + public function isGenerated(){ + return ($this->flags & self::FLAG_GENERATED) > 0; + } + + /** + * @return bool + */ + public function isPopulated(){ + return ($this->flags & self::FLAG_POPULATED) > 0; + } + + /** + * @param bool $value + */ + public function setGenerated($value = true){ + $this->flags = ($this->flags & ~self::FLAG_GENERATED) | ($value === true ? self::FLAG_GENERATED : 0); + } + + /** + * @param bool $value + */ + public function setPopulated($value = true){ + $this->flags = ($this->flags & ~self::FLAG_POPULATED) | ($value === true ? self::FLAG_POPULATED : 0); + } + + /** + * @param int $x 0-15 + * @param int $y 0-127 + * @param int $z 0-15 + * + * @return int 0-255 + */ + public function getBlockId($x, $y, $z){ + return ord(@$this->ids[$y >> 4]{(($y & 0x0f) << 8) + ($z << 4) + $x}); + } + + /** + * @param int $x 0-15 + * @param int $y 0-127 + * @param int $z 0-15 + * @param int $blockId 0-255 + */ + public function setBlockId($x, $y, $z, $blockId){ + @$this->ids[$y >> 4]{(($y & 0x0f) << 8) + ($z << 4) + $x} = chr($blockId); + } + + /** + * @param int $x 0-15 + * @param int $y 0-127 + * @param int $z 0-15 + * + * @return int 0-15 + */ + public function getBlockData($x, $y, $z){ + $m = ord($this->meta[$y >> 4]{(($y & 0x0f) << 7) + ($z << 3) + ($x >> 1)}); + if(($y & 1) === 0){ + return $m & 0x0F; + }else{ + return $m >> 4; + } + } + + /** + * @param int $x 0-15 + * @param int $y 0-127 + * @param int $z 0-15 + * @param int $data 0-15 + */ + public function setBlockData($x, $y, $z, $data){ + $i = (($y & 0x0f) << 7) + ($z << 3) + ($x >> 1); + $old_m = ord($this->meta[$y >> 4]{$i}); + if(($y & 1) === 0){ + $this->meta[$y >> 4]{$i} = chr(($old_m & 0xf0) | ($data & 0x0f)); + }else{ + $this->meta[$y >> 4]{$i} = chr((($data & 0x0f) << 4) | ($old_m & 0x0f)); + } + } + + /** + * @param int $y 0-7 + * + * @return string + */ + public function getSectionIds($y){ + return $this->ids[$y]; + } + + /** + * @param int $y 0-7 + * + * @return string + */ + public function getSectionData($y){ + return $this->meta[$y]; + } + + /** + * @param int $y 0-7 + * @param string $ids + * @param string $meta + */ + public function setSection($y, $ids = null, $meta = null){ + if($ids !== null){ + $this->ids[$y] = $ids; + } + + if($meta !== null){ + $this->meta[$y] = $meta; + } + } + + /** + * @return string + */ + public function toBinary(){ + $binary = Binary::writeInt($this->x) . Binary::writeInt($this->z) . chr($this->flags); + if($this->isGenerated()){ + for($y = 0; $y < self::$HEIGHT; ++$y){ + $binary .= $this->getSectionIds($y); + $binary .= $this->getSectionData($y); + } + } + + return $binary; + } + + /** + * @param string $binary + * + * @return SimpleChunk + */ + public static function fromBinary($binary){ + $offset = 0; + $chunkX = Binary::readInt(substr($binary, $offset, 4)); + $offset += 4; + $chunkZ = Binary::readInt(substr($binary, $offset, 4)); + $offset += 4; + $flags = ord($binary{$offset++}); + $ids = []; + $meta = []; + if(($flags & self::FLAG_GENERATED) > 0){ + for($y = 0; $y < self::$HEIGHT; ++$y){ + $ids[$y] = substr($binary, $offset, 4096); + $offset += 4096; + $meta[$y] = substr($binary, $offset, 2048); + $offset += 2048; + } + } + return new SimpleChunk($chunkX, $chunkZ, $flags, $ids, $meta); + } + +} \ No newline at end of file diff --git a/src/pocketmine/level/format/anvil/Anvil.php b/src/pocketmine/level/format/anvil/Anvil.php index b7ce9d6a1..8fa0e2c7e 100644 --- a/src/pocketmine/level/format/anvil/Anvil.php +++ b/src/pocketmine/level/format/anvil/Anvil.php @@ -22,20 +22,122 @@ namespace pocketmine\level\format\anvil; use pocketmine\level\format\generic\BaseLevelProvider; +use pocketmine\level\Level; +use pocketmine\Player; class Anvil extends BaseLevelProvider{ - protected $basePath; - public function __construct($path, $levelName){ - $this->basePath = realpath($path) . "/"; - } + /** @var RegionLoader */ + protected $regions = []; + + /** @var Chunk[] */ + protected $chunks = []; + public static function isValid($path){ - return file_exists(realpath($path) . "region/"); + return file_exists(realpath($path) . "level.dat") and file_exists(realpath($path) . "region/"); } public static function getRegionIndex($chunkX, $chunkZ, &$x, &$z){ $x = $chunkX >> 5; $z = $chunkZ >> 5; } + + public function unloadChunks(){ + $this->chunks = []; + } + + 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){ + $index = Level::chunkHash($chunkX, $chunkZ); + if(isset($this->chunks[$index])){ + return true; + } + $regionX = $regionZ = null; + self::getRegionIndex($chunkX, $chunkZ, $regionX, $regionZ); + $this->loadRegion($regionX, $regionZ); + $chunk = $this->getRegion($regionX, $regionZ)->readChunk($chunkX - $regionX * 32, $chunkZ - $regionZ * 32, true); //generate empty chunk if not loaded + if($chunk instanceof Chunk){ + $this->chunks[$index] = $chunk; + }else{ + return false; + } + } + + public function unloadChunk($x, $z, $safe = true){ + if($safe === true and $this->isChunkLoaded($x, $z)){ + $chunk = $this->getChunk($x, $z); + foreach($chunk->getEntities() as $entity){ + if($entity instanceof Player){ + return false; + } + } + } + + unset($this->chunks[Level::chunkHash($x, $z)]); + return true; + } + + public function saveChunk($x, $z){ + if($this->isChunkLoaded($x, $z)){ + $this->getRegion($x >> 5, $z >> 5)->writeChunk($this->getChunk($x, $z)); + return true; + } + + return false; + } + + /** + * @param $x + * @param $z + * + * @return RegionLoader + */ + protected function getRegion($x, $z){ + $index = $x.":".$z; + return isset($this->regions[$index]) ? $this->regions[$index] : null; + } + + public function getChunk($chunkX, $chunkZ, $create = false){ + $index = Level::chunkHash($chunkX, $chunkZ); + if(isset($this->chunks[$index])){ + return $this->chunks[$index]; + }elseif($create !== true){ + return null; + } + + $this->loadChunk($chunkX, $chunkZ); + return $this->getChunk($chunkX, $chunkZ, false); + } + + public function isChunkGenerated($chunkX, $chunkZ){ + if(($region = $this->getRegion($chunkX >> 5, $chunkZ >> 5)) instanceof RegionLoader){ + return $region->chunkExists($chunkX - $region->getX() * 32, $chunkZ - $region->getZ() * 32); + } + return false; + } + + protected function loadRegion($x, $z){ + $index = $x.":".$z; + if(isset($this->regions[$index])){ + return true; + } + + $this->regions[$index] = new RegionLoader($this, $x, $z); + + return true; + } } \ No newline at end of file diff --git a/src/pocketmine/level/format/anvil/Chunk.php b/src/pocketmine/level/format/anvil/Chunk.php index 6667edfb4..859ed984b 100644 --- a/src/pocketmine/level/format/anvil/Chunk.php +++ b/src/pocketmine/level/format/anvil/Chunk.php @@ -23,6 +23,7 @@ namespace pocketmine\level\format\anvil; use pocketmine\level\format\generic\BaseChunk; use pocketmine\level\format\generic\EmptyChunkSection; +use pocketmine\level\format\LevelProvider; use pocketmine\level\Level; use pocketmine\nbt\NBT; use pocketmine\nbt\tag\Compound; @@ -33,7 +34,7 @@ class Chunk extends BaseChunk{ /** @var Compound */ protected $nbt; - public function __construct(Level $level, Compound $nbt){ + public function __construct(LevelProvider $level, Compound $nbt){ $this->nbt = $nbt; if($this->nbt->Entities instanceof Enum){ @@ -71,7 +72,7 @@ class Chunk extends BaseChunk{ } } - parent::__construct($level, $this->nbt["xPos"], $this->nbt["zPos"], $sections); + parent::__construct($level, $this->nbt["xPos"], $this->nbt["zPos"], $sections, $this->nbt["Entities"], $this->nbt["TileEntities"]); } public function getChunkSnapshot($includeMaxBlockY = true, $includeBiome = false, $includeBiomeTemp = false){ @@ -101,6 +102,14 @@ class Chunk extends BaseChunk{ //TODO: maxBlockY, biomeMap, biomeTemp - return new ChunkSnapshot($this->getX(), $this->getZ(), $this->getLevel()->getName(), $this->getLevel()->getTime(), $blockId, $blockData, $blockSkyLight, $blockLight, $emptySections, null, null, null, null); + //TODO: time + return new ChunkSnapshot($this->getX(), $this->getZ(), $this->getLevel()->getName(), 0/*$this->getLevel()->getTime()*/, $blockId, $blockData, $blockSkyLight, $blockLight, $emptySections, null, null, null, null); + } + + /** + * @return Compound + */ + public function getNBT(){ + return $this->nbt; } } \ No newline at end of file diff --git a/src/pocketmine/level/format/anvil/ChunkSection.php b/src/pocketmine/level/format/anvil/ChunkSection.php index ebe1cf469..8ddc2c6fa 100644 --- a/src/pocketmine/level/format/anvil/ChunkSection.php +++ b/src/pocketmine/level/format/anvil/ChunkSection.php @@ -39,6 +39,10 @@ class ChunkSection implements \pocketmine\level\format\ChunkSection{ $this->skyLight = (string) $nbt["SkyLight"]; } + public function getY(){ + return $this->y; + } + public function getBlockId($x, $y, $z){ return ord($this->blocks{($y << 8) + ($z << 4) + $x}); } diff --git a/src/pocketmine/level/format/anvil/RegionLoader.php b/src/pocketmine/level/format/anvil/RegionLoader.php index 9af563397..7a682828e 100644 --- a/src/pocketmine/level/format/anvil/RegionLoader.php +++ b/src/pocketmine/level/format/anvil/RegionLoader.php @@ -21,6 +21,7 @@ namespace pocketmine\level\format\anvil; +use pocketmine\level\format\LevelProvider; use pocketmine\nbt\NBT; use pocketmine\nbt\tag\Byte; use pocketmine\nbt\tag\ByteArray; @@ -29,6 +30,7 @@ use pocketmine\nbt\tag\Enum; use pocketmine\nbt\tag\Int; use pocketmine\nbt\tag\IntArray; use pocketmine\nbt\tag\Long; +use pocketmine\Player; use pocketmine\utils\Binary; class RegionLoader{ @@ -42,14 +44,15 @@ class RegionLoader{ protected $filePath; protected $filePointer; protected $lastSector; + /** @var LevelProvider */ + protected $levelProvider; protected $locationTable = []; - public function __construct($path, /*Level $level, */ - $regionX, $regionZ){ + public function __construct(LevelProvider $level, $regionX, $regionZ){ $this->x = $regionX; $this->z = $regionZ; - $this->filePath = /*$level->getPath()*/ - $path . "region/r.$regionX.$regionZ.mca"; + $this->levelProvider = $level; + $this->filePath = $this->levelProvider->getPath() . "region/r.$regionX.$regionZ.mca"; touch($this->filePath); $this->filePointer = fopen($this->filePath, "r+b"); flock($this->filePointer, LOCK_EX); @@ -71,13 +74,17 @@ class RegionLoader{ } } + protected function isChunkGenerated($index){ + return !($this->locationTable[$index][0] === 0 or $this->locationTable[$index][1] === 0); + } + public function readChunk($x, $z, $generate = true){ $index = self::getChunkOffset($x, $z); if($index < 0 or $index >= 4096){ return false; } - if($this->locationTable[$index][0] === 0 or $this->locationTable[$index][1] === 0){ + if(!$this->isChunkGenerated($index)){ if($generate === true){ //Allocate space $this->locationTable[$index][0] = ++$this->lastSector; @@ -116,8 +123,11 @@ class RegionLoader{ return false; } - return $chunk; - //$chunk = new Chunk($level, $chunk); + return new Chunk($this->levelProvider, $chunk); + } + + public function chunkExists($x, $z){ + return $this->isChunkGenerated(self::getChunkOffset($x, $z)); } public function generateChunk($x, $z){ @@ -131,6 +141,8 @@ class RegionLoader{ $nbt->InhabitedTime = new Long("InhabitedTime", 0); $nbt->Biomes = new ByteArray("Biomes", str_repeat(Binary::writeByte(-1), 256)); $nbt->HeightMap = new IntArray("HeightMap", array_fill(0, 256, 127)); + //TODO: check type and name + //$nbt->GrassMap = new IntArray("GrassMap", array_fill(0, 256, 127)); $nbt->Sections = new Enum("Sections", []); $nbt->Sections->setTagType(NBT::TAG_Compound); $nbt->Entities = new Enum("Entities", []); @@ -139,6 +151,10 @@ class RegionLoader{ $nbt->TileEntities->setTagType(NBT::TAG_Compound); $nbt->TileTicks = new Enum("TileTicks", []); $nbt->TileTicks->setTagType(NBT::TAG_Compound); + $this->saveChunk($x, $z, $nbt); + } + + protected function saveChunk($x, $z, Compound $nbt){ $writer = new NBT(NBT::BIG_ENDIAN); $writer->setData(new Compound("", array($nbt))); $chunkData = $writer->writeCompressed(self::COMPRESSION_ZLIB, self::$COMPRESSION_LEVEL); @@ -154,6 +170,47 @@ class RegionLoader{ fwrite($this->filePointer, str_pad(Binary::writeInt($length) . chr(self::COMPRESSION_ZLIB) . $chunkData, $sectors << 12, "\x00", STR_PAD_RIGHT)); } + public function writeChunk(Chunk $chunk){ + $nbt = $chunk->getNBT(); + $nbt->Sections = new Enum("Sections", []); + $nbt->Sections->setTagType(NBT::TAG_Compound); + foreach($chunk->getSections() as $section){ + $nbt->Sections[$section->getY()] = new Compound(null, [ + "Y" => new Byte("Y", $section->getY()), + "Blocks" => new ByteArray("Blocks", $section->getIdArray()), + "Data" => new ByteArray("Data", $section->getDataArray()), + "BlockLight" => new ByteArray("BlockLight", $section->getLightArray()), + "SkyLight" => new ByteArray("SkyLight", $section->getSkyLightArray()) + ]); + } + + $entities = []; + + foreach($chunk->getEntities() as $entity){ + if(!($entity instanceof Player) and $entity->closed !== true){ + $entity->saveNBT(); + $entities[] = $entity->namedtag; + } + } + + $nbt->Entities = new Enum("Entities", $entities); + $nbt->Entities->setTagType(NBT::TAG_Compound); + + + $tiles = []; + foreach($chunk->getTiles() as $tile){ + if($tile->closed !== true){ + $tile->saveNBT(); + $tiles[] = $tile->namedtag; + } + } + + $nbt->Entities = new Enum("TileEntities", $tiles); + $nbt->Entities->setTagType(NBT::TAG_Compound); + + $this->saveChunk($chunk->getX() - ($this->getX() * 32), $chunk->getZ() - ($this->getZ() * 32), $nbt); + } + protected static function getChunkOffset($x, $z){ return $x + ($z << 5); } diff --git a/src/pocketmine/level/format/generic/BaseChunk.php b/src/pocketmine/level/format/generic/BaseChunk.php index d1169e5bc..4e39884a7 100644 --- a/src/pocketmine/level/format/generic/BaseChunk.php +++ b/src/pocketmine/level/format/generic/BaseChunk.php @@ -21,27 +21,46 @@ namespace pocketmine\level\format\generic; +use pocketmine\entity\DroppedItem; +use pocketmine\entity\Entity; use pocketmine\level\format\Chunk; use pocketmine\level\format\ChunkSection; +use pocketmine\level\format\LevelProvider; use pocketmine\level\Level; +use pocketmine\nbt\tag\Compound; +use pocketmine\nbt\tag\String; +use pocketmine\tile\Chest; +use pocketmine\tile\Furnace; +use pocketmine\tile\Sign; +use pocketmine\tile\Tile; abstract class BaseChunk implements Chunk{ /** @var ChunkSection[] */ protected $sections = []; + + /** @var Entity[] */ + protected $entities = []; + + /** @var Tile[] */ + protected $tiles = []; + + /** @var \WeakRef */ protected $level; protected $x; protected $z; /** - * @param Level $level + * @param LevelProvider $level * @param int $x * @param int $z * @param ChunkSection[] $sections + * @param Compound[] $entities + * @param Compound[] $tiles */ - public function __construct(Level $level, $x, $z, array $sections){ - $this->level = $level; + protected function __construct(LevelProvider $level, $x, $z, array $sections, array $entities = [], array $tiles = []){ + $this->level = new \WeakRef($level); $this->x = (int) $x; $this->z = (int) $z; foreach($sections as $Y => $section){ @@ -59,6 +78,44 @@ abstract class BaseChunk implements Chunk{ return; } } + + foreach($entities as $nbt){ + if($nbt instanceof Compound){ + if(!isset($nbt->id)){ + continue; + } + + if($nbt->id instanceof String){ //New format + switch($nbt["id"]){ + case "Item": + (new DroppedItem($this, $nbt))->spawnToAll(); + break; + } + }else{ //Old format + + } + } + } + + + foreach($tiles as $nbt){ + if($nbt instanceof Compound){ + if(!isset($nbt->id)){ + continue; + } + switch($nbt["id"]){ + case Tile::CHEST: + new Chest($this, $nbt); + break; + case Tile::FURNACE: + new Furnace($this, $nbt); + break; + case Tile::SIGN: + new Sign($this, $nbt); + break; + } + } + } } public function getX(){ @@ -69,8 +126,11 @@ abstract class BaseChunk implements Chunk{ return $this->z; } + /** + * @return LevelProvider + */ public function getLevel(){ - return $this->level; + return $this->level->valid() ? $this->level->get() : null; } public function getBlock($x, $y, $z, &$blockId, &$meta = null){ @@ -140,6 +200,59 @@ abstract class BaseChunk implements Chunk{ $this->sections[(int) $fY] = $section; } + public function addEntity(Entity $entity){ + $this->entities[$entity->getID()] = $entity; + } + + public function removeEntity(Entity $entity){ + unset($this->entities[$entity->getID()]); + } + + public function addTile(Tile $tile){ + $this->tiles[$tile->getID()] = $tile; + } + + public function removeTile(Tile $tile){ + unset($this->tiles[$tile->getID()]); + } + + public function getEntities(){ + return $this->entities; + } + + public function getTiles(){ + return $this->tiles; + } + + public function isLoaded(){ + return $this->getLevel() === null ? false : $this->getLevel()->isChunkLoaded($this->getX(), $this->getZ()); + } + + public function load($generate = true){ + return $this->getLevel() === null ? false : $this->getLevel()->getChunk($this->getX(), $this->getZ(), true) instanceof Chunk; + } + + public function unload($save = true, $safe = true){ + $level = $this->getLevel(); + if($level === null){ + return true; + } + if($save === true){ + $level->saveChunk($this->getX(), $this->getZ()); + } + if($this->getLevel()->unloadChunk($this->getX(), $this->getZ(), $safe)){ + foreach($this->getEntities() as $entity){ + $entity->close(); + } + foreach($this->getTiles() as $tile){ + $tile->close(); + } + } + } + + /** + * @return ChunkSection[] + */ public function getSections(){ return $this->sections; } diff --git a/src/pocketmine/level/format/generic/BaseLevelProvider.php b/src/pocketmine/level/format/generic/BaseLevelProvider.php index 87245615a..cc9976fae 100644 --- a/src/pocketmine/level/format/generic/BaseLevelProvider.php +++ b/src/pocketmine/level/format/generic/BaseLevelProvider.php @@ -21,16 +21,83 @@ namespace pocketmine\level\format\generic; use pocketmine\level\format\LevelProvider; -use pocketmine\Server; +use pocketmine\level\Level; +use pocketmine\math\Vector3; +use pocketmine\nbt\NBT; +use pocketmine\nbt\tag\Compound; +use pocketmine\nbt\tag\Int; abstract class BaseLevelProvider implements LevelProvider{ - /** @var Server */ - protected $server; + /** @var Level */ + protected $level; /** @var string */ protected $path; + /** @var Compound */ + protected $levelData; - public function __construct(Server $server, $path){ - $this->server = $server; + public function __construct(Level $level, $path){ + $this->level = $level->getServer(); $this->path = $path; + @mkdir($this->path, 0777, true); + $nbt = new NBT(NBT::BIG_ENDIAN); + $nbt->readCompressed(file_get_contents($this->getPath() . "level.dat")); + $levelData = $nbt->getData(); + if($levelData->Data instanceof Compound){ + $this->levelData = $levelData->Data; + }else{ + throw new \Exception("Invalid level.dat"); + } } + + public function getPath(){ + return $this->path; + } + + public function getServer(){ + return $this->level->getServer(); + } + + public function getLevel(){ + return $this->level; + } + + public function getName(){ + return $this->levelData["LevelName"]; + } + + public function getTime(){ + return $this->levelData["Time"]; + } + + public function setTime($value){ + $this->levelData->Time = new Int("Time", (int) $value); + } + + public function getSpawn(){ + return new Vector3($this->levelData["SpawnX"], $this->levelData["SpawnY"], $this->levelData["SpawnZ"]); + } + + public function setSpawn(Vector3 $pos){ + $this->levelData->SpawnX = new Int("SpawnX", $pos->x); + $this->levelData->SpawnY = new Int("SpawnY", $pos->y); + $this->levelData->SpawnZ = new Int("SpawnZ", $pos->z); + } + + /** + * @return Compound + */ + public function getLevelData(){ + return $this->levelData; + } + + public function saveLevelData(){ + $nbt = new NBT(NBT::BIG_ENDIAN); + $nbt->setData(new Compound(null, [ + "Data" => $this->levelData + ])); + $buffer = $nbt->writeCompressed(); + @file_put_contents($this->getPath() . "level.dat", $buffer); + } + + } \ No newline at end of file diff --git a/src/pocketmine/level/generator/Flat.php b/src/pocketmine/level/generator/Flat.php index b9734b6b0..7c0b5f1dc 100644 --- a/src/pocketmine/level/generator/Flat.php +++ b/src/pocketmine/level/generator/Flat.php @@ -21,7 +21,7 @@ namespace pocketmine\level\generator; -use pocketmine\block\Air; +use pocketmine\level\generator\populator\Populator; use pocketmine\block\CoalOre; use pocketmine\block\DiamondOre; use pocketmine\block\Dirt; @@ -31,13 +31,22 @@ use pocketmine\block\IronOre; use pocketmine\block\LapisOre; use pocketmine\block\RedstoneOre; use pocketmine\item\Item; +use pocketmine\level\format\SimpleChunk; use pocketmine\level\generator\populator\Ore; use pocketmine\level\Level; use pocketmine\math\Vector3 as Vector3; use pocketmine\utils\Random; class Flat extends Generator{ - private $level, $random, $structure, $chunks, $options, $floorLevel, $preset, $populators = []; + /** @var GenerationChunkManager */ + private $level; + /** @var SimpleChunk */ + private $chunk; + /** @var Random */ + private $random; + /** @var Populator[] */ + private $populators = []; + private $structure, $chunks, $options, $floorLevel, $preset; public function getSettings(){ return $this->options; @@ -75,7 +84,7 @@ class Flat extends Generator{ }*/ } - public function parsePreset($preset){ + protected function parsePreset($preset){ $this->preset = $preset; $preset = explode(";", $preset); $version = (int) $preset[0]; @@ -90,30 +99,33 @@ class Flat extends Generator{ $b = Item::fromString($b); $cnt = $matches[2][$i] === "" ? 1 : intval($matches[2][$i]); for($cY = $y, $y += $cnt; $cY < $y; ++$cY){ - $this->structure[$cY] = $b; + $this->structure[$cY] = [$b->getID(),$b->getDamage()]; } } $this->floorLevel = $y; for(; $y < 0xFF; ++$y){ - $this->structure[$y] = new Air(); + $this->structure[$y] = [0, 0]; } + $this->chunk = new SimpleChunk(null, null, SimpleChunk::FLAG_GENERATED); + for($Y = 0; $Y < 8; ++$Y){ $this->chunks[$Y] = ""; $startY = $Y << 4; $endY = $startY + 16; for($Z = 0; $Z < 16; ++$Z){ for($X = 0; $X < 16; ++$X){ - $blocks = ""; - $metas = ""; for($y = $startY; $y < $endY; ++$y){ - $blocks .= chr($this->structure[$y]->getID()); - $metas .= substr(dechex($this->structure[$y]->getDamage()), -1); + if($this->structure[$y][0] !== 0){ + $this->chunk->setBlockId($X, $y, $Z, $this->structure[$y][0]); + } + if($this->structure[$y][0] !== 0){ + $this->chunk->setBlockData($X, $y, $Z, $this->structure[$y][1]); + } } - $this->chunks[$Y] .= $blocks . hex2bin($metas) . "\x00\x00\x00\x00\x00\x00\x00\x00"; } } } @@ -135,15 +147,16 @@ class Flat extends Generator{ } } - public function init(Level $level, Random $random){ + public function init(GenerationChunkManager $level, Random $random){ $this->level = $level; $this->random = $random; } public function generateChunk($chunkX, $chunkZ){ - for($Y = 0; $Y < 8; ++$Y){ - $this->level->setMiniChunk($chunkX, $chunkZ, $Y, $this->chunks[$Y]); - } + $chunk = clone $this->chunk; + $chunk->setX($chunkX); + $chunk->setZ($chunkZ); + $this->level->setChunk($chunkX, $chunkZ, $chunk); } public function populateChunk($chunkX, $chunkZ){ diff --git a/src/pocketmine/level/generator/GenerationChunkManager.php b/src/pocketmine/level/generator/GenerationChunkManager.php new file mode 100644 index 000000000..fc738f036 --- /dev/null +++ b/src/pocketmine/level/generator/GenerationChunkManager.php @@ -0,0 +1,187 @@ +levelID = $levelID; + $this->seed = $seed; + $this->manager = $manager; + + $this->generator = new $class($options); + $this->generator->init($this, new Random($seed)); + } + + /** + * @return int + */ + public function getSeed(){ + return $this->seed; + } + + /** + * @return int + */ + public function getID(){ + return $this->levelID; + } + + /** + * @param $chunkX + * @param $chunkZ + * + * @return SimpleChunk + */ + public function getChunk($chunkX, $chunkZ){ + $index = Level::chunkHash($chunkX, $chunkZ); + return !isset($this->chunks[$index]) ? $this->requestChunk($chunkX, $chunkZ) : $this->chunks[$index]; + } + + public function generateChunk($chunkX, $chunkZ){ + $this->chunks[Level::chunkHash($chunkX, $chunkZ)] = new SimpleChunk($chunkX, $chunkZ, 0); + $this->generator->generateChunk($chunkX, $chunkZ); + } + + public function populateChunk($chunkX, $chunkZ){ + if(!$this->isChunkGenerated($chunkX, $chunkZ)){ + $this->generateChunk($chunkX, $chunkZ); + } + + for($z = $chunkZ - 1; $z <= $chunkZ + 1; ++$z){ + for($x = $chunkX - 1; $x <= $chunkX + 1; ++$x){ + if(!$this->isChunkGenerated($x, $z)){ + $this->generateChunk($x, $z); + } + } + } + + $this->generator->populateChunk($chunkX, $chunkZ); + } + + public function isChunkGenerated($chunkX, $chunkZ){ + return $this->getChunk($chunkX, $chunkZ)->isGenerated(); + } + + public function isChunkPopulated($chunkX, $chunkZ){ + return $this->getChunk($chunkX, $chunkZ)->isPopulated(); + } + + protected function requestChunk($chunkX, $chunkZ){ + $chunk = $this->manager->requestChunk($this->levelID, $chunkX, $chunkZ); + $this->chunks[Level::chunkHash($chunkX, $chunkZ)] = $chunk; + return $chunk; + } + + /** + * @param int $chunkX + * @param int $chunkZ + * @param SimpleChunk $chunk + */ + public function setChunk($chunkX, $chunkZ, SimpleChunk $chunk){ + $this->chunks[Level::chunkHash($chunkX, $chunkZ)] = $chunk; + if($chunk->isGenerated() and $chunk->isPopulated()){ + //TODO: Queue to be sent + } + } + + /** + * Gets the raw block id. + * + * @param int $x + * @param int $y + * @param int $z + * + * @return int 0-255 + */ + public function getBlockIdAt($x, $y, $z){ + return $this->getChunk($x >> 4, $z >> 4)->getBlockId($x & 0x0f, $y & 0x7f, $z & 0x0f); + } + + /** + * Sets the raw block id. + * + * @param int $x + * @param int $y + * @param int $z + * @param int $id 0-255 + */ + public function setBlockIdAt($x, $y, $z, $id){ + $this->getChunk($x >> 4, $z >> 4)->setBlockId($x & 0x0f, $y & 0x7f, $z & 0x0f, $id & 0xff); + } + + /** + * Gets the raw block metadata + * + * @param int $x + * @param int $y + * @param int $z + * + * @return int 0-15 + */ + public function getBlockDataAt($x, $y, $z){ + return $this->getChunk($x >> 4, $z >> 4)->getBlockData($x & 0x0f, $y & 0x7f, $z & 0x0f); + } + + /** + * Sets the raw block metadata. + * + * @param int $x + * @param int $y + * @param int $z + * @param int $data 0-15 + */ + public function setBlockDataAt($x, $y, $z, $data){ + $this->getChunk($x >> 4, $z >> 4)->setBlockData($x & 0x0f, $y & 0x7f, $z & 0x0f, $data & 0x0f); + } + + public function shutdown(){ + foreach($this->chunks as $chunk){ + //TODO: send generated chunks to be saved + } + } + + +} \ No newline at end of file diff --git a/src/pocketmine/level/generator/GenerationManager.php b/src/pocketmine/level/generator/GenerationManager.php new file mode 100644 index 000000000..f7621ed77 --- /dev/null +++ b/src/pocketmine/level/generator/GenerationManager.php @@ -0,0 +1,257 @@ +Thread + * byte[] payload: + * string root namespace + * byte[] path + */ + const PACKET_ADD_NAMESPACE = 0x00; + + /* + * Direction: Both + * If Server->Thread, request chunk generation + * If Thread->Server, request chunk contents / loading + * byte[] payload: + * int32 levelID + * int32 chunkX + * int32 chunkZ + */ + const PACKET_REQUEST_CHUNK = 0x01; + + /* + * Direction: Both + * byte[] payload: + * int32 levelID + * int32 chunkX + * int32 chunkZ + * byte flags (1 generated, 2 populated) + * byte[] chunk (none if generated flag is not set) + */ + const PACKET_SEND_CHUNK = 0x02; + + /* + * Direction: Server->Thread + * byte[] payload: + * int32 levelID + * int32 seed + * string class that extends pocketmine\level\generator\Generator + * byte[] serialized options array + */ + const PACKET_OPEN_LEVEL = 0x03; + + /* + * Direction: Server->Thread + * byte[] payload: + * int32 levelID + */ + const PACKET_CLOSE_LEVEL = 0x04; + + /* + * Direction: Server->Thread + * no payload + */ + const PACKET_SHUTDOWN = 0xff; + + + + protected $socket; + /** @var \Logger */ + protected $logger; + /** @var \SplAutoLoader */ + protected $loader; + + /** @var GenerationChunkManager[] */ + protected $levels = []; + + /** @var \SplQueue */ + protected $requestQueue; + + protected $needsChunk = null; + + protected $shutdown = false; + + /** + * @param resource $socket + * @param \Logger $logger + * @param \SplAutoloader $loader + */ + public function __construct($socket, \Logger $logger, \SplAutoloader $loader){ + $this->socket = $socket; + $this->logger = $logger; + $this->loader = $loader; + $this->requestQueue = new \SplQueue(); + + while($this->shutdown !== true){ + if($this->requestQueue->count() > 0){ + $r = $this->requestQueue->dequeue(); + $levelID = $r[0]; + $chunkX = $r[1]; + $chunkZ = $r[2]; + $this->generateChunk($levelID, $chunkX, $chunkZ); + + } + $this->readPacket(); + } + } + + protected function openLevel($levelID, $seed, $class, array $options){ + if(!isset($this->levels[$levelID])){ + $this->levels[$levelID] = new GenerationChunkManager($this, $levelID, $seed, $class, $options); + } + } + + protected function generateChunk($levelID, $chunkX, $chunkZ){ + if(isset($this->levels[$levelID])){ + $this->levels[$levelID]->populateChunk($chunkX, $chunkZ); //Request population directly + //TODO: wait for queue generation (to wait for extra chunk changes) + } + } + + protected function closeLevel($levelID){ + if(!isset($this->levels[$levelID])){ + $this->levels[$levelID]->shutdown(); + unset($this->levels[$levelID]); + } + } + + protected function enqueueChunk($levelID, $chunkX, $chunkZ){ + $this->requestQueue->enqueue([$levelID, $chunkX, $chunkZ]); + } + + protected function receiveChunk($levelID, SimpleChunk $chunk){ + if($this->needsChunk !== null and $this->needsChunk[0] === $levelID){ + if($this->needsChunk[1] === $chunk->getX() and $this->needsChunk[2] === $chunk->getZ()){ + $this->needsChunk = $chunk; + } + } + //TODO: set new received chunks + } + + /** + * @param $levelID + * @param $chunkX + * @param $chunkZ + * + * @return SimpleChunk + */ + public function requestChunk($levelID, $chunkX, $chunkZ){ + $this->needsChunk = [$levelID, $chunkX, $chunkZ]; + $binary = chr(self::PACKET_REQUEST_CHUNK . Binary::writeInt($levelID) . Binary::writeInt($chunkX) . Binary::writeInt($chunkZ)); + @socket_write($this->socket, Binary::writeInt(strlen($binary)) . $binary); + do{ + $this->readPacket(); + }while($this->shutdown !== true and !($this->needsChunk instanceof SimpleChunk)); + + $chunk = $this->needsChunk; + $this->needsChunk = null; + if($chunk instanceof SimpleChunk){ + return $chunk; + }else{ + return new SimpleChunk($chunkX, $chunkZ, 0); + } + } + + public function sendChunk($levelID, SimpleChunk $chunk){ + $binary = chr(self::PACKET_SEND_CHUNK . Binary::writeInt($levelID) . $chunk->toBinary()); + @socket_write($this->socket, Binary::writeInt(strlen($binary)) . $binary); + } + + protected function readPacket(){ + $len = socket_read($this->socket, 4); + if($len === false or $len === ""){ + usleep(5000); + return; + } + $packet = socket_read($this->socket, Binary::readInt($len)); + $pid = ord($packet{0}); + $offset = 1; + if($pid === self::PACKET_REQUEST_CHUNK){ + $levelID = Binary::readInt(substr($packet, $offset, 4)); + $offset += 4; + $chunkX = Binary::readInt(substr($packet, $offset, 4)); + $offset += 4; + $chunkZ = Binary::readInt(substr($packet, $offset, 4)); + $this->enqueueChunk($levelID, $chunkX, $chunkZ); + + }elseif($pid === self::PACKET_SEND_CHUNK){ + $levelID = Binary::readInt(substr($packet, $offset, 4)); + $offset += 4; + $chunk = SimpleChunk::fromBinary(substr($packet, $offset)); + $this->receiveChunk($levelID, $chunk); + + }elseif($pid === self::PACKET_OPEN_LEVEL){ + $levelID = Binary::readInt(substr($packet, $offset, 4)); + $offset += 4; + $seed = Binary::readInt(substr($packet, $offset, 4)); + $offset += 4; + $len = Binary::readShort(substr($packet, $offset, 2)); + $offset += 2; + $class = substr($packet, $offset, $len); + $offset += $len; + $options = unserialize(substr($packet, $offset)); + $this->openLevel($levelID, $seed, $class, $options); + + }elseif($pid === self::PACKET_CLOSE_LEVEL){ + $levelID = Binary::readInt(substr($packet, $offset, 4)); + $this->closeLevel($levelID); + }elseif($pid === self::PACKET_ADD_NAMESPACE){ + $len = Binary::readShort(substr($packet, $offset, 2)); + $offset += 2; + $namespace = substr($packet, $offset, $len); + $offset += $len; + $path = substr($packet, $offset); + $this->loader->add($namespace, [$path]); + }elseif($pid === self::PACKET_SHUTDOWN){ + foreach($this->levels as $level){ + $level->shutdown(); + } + $this->levels = []; + + $this->shutdown = true; + socket_close($this->socket); + } + } + + /** + * @return \Logger + */ + public function getLogger(){ + return $this->logger; + } + +} \ No newline at end of file diff --git a/src/pocketmine/level/generator/GenerationThread.php b/src/pocketmine/level/generator/GenerationThread.php new file mode 100644 index 000000000..e20bdf93d --- /dev/null +++ b/src/pocketmine/level/generator/GenerationThread.php @@ -0,0 +1,96 @@ +externalSocket; + } + + public function getInternalSocket(){ + return $this->internalSocket; + } + + /** + * @return \ThreadedLogger + */ + public function getLogger(){ + return $this->logger; + } + + public function __construct(\ThreadedLogger $logger, \SplAutoloader $loader){ + $this->loader = $loader; + $loadPaths = []; + $this->addDependency($loadPaths, new \ReflectionClass($this->logger)); + $this->addDependency($loadPaths, new \ReflectionClass($this->loader)); + $this->loadPaths = array_reverse($loadPaths); + + $sockets = []; + if(!socket_create_pair((strtoupper(substr(PHP_OS, 0, 3)) === 'WIN' ? AF_INET : AF_UNIX), SOCK_STREAM, 0, $sockets)){ + throw new \Exception("Could not create IPC sockets. Reason: ".socket_strerror(socket_last_error())); + } + + $this->internalSocket = $sockets[0]; + socket_set_block($this->internalSocket); //IMPORTANT! + $this->externalSocket = $sockets[1]; + socket_set_nonblock($this->externalSocket); + + $this->start(PTHREADS_INHERIT_ALL & ~PTHREADS_INHERIT_CLASSES); + } + + protected function addDependency(array &$loadPaths, \ReflectionClass $dep){ + if($dep->getFileName() !== false){ + $loadPaths[$dep->getName()] = $dep->getFileName(); + } + + if($dep->getParentClass() instanceof \ReflectionClass){ + $this->addDependency($loadPaths, $dep->getParentClass()); + } + + foreach($dep->getInterfaces() as $interface){ + $this->addDependency($loadPaths, $interface); + } + } + + public function run(){ + //Load removed dependencies, can't use require_once() + foreach($this->loadPaths as $name => $path){ + if(!class_exists($name, false) and !class_exists($name, false)){ + require($path); + } + } + $this->loader->register(); + + $generationManager = new GenerationManager($this->getInternalSocket(), $this->getLogger(), $this->loader); + } +} \ No newline at end of file diff --git a/src/pocketmine/level/generator/Generator.php b/src/pocketmine/level/generator/Generator.php index e4fb59b10..12cc70ee4 100644 --- a/src/pocketmine/level/generator/Generator.php +++ b/src/pocketmine/level/generator/Generator.php @@ -50,7 +50,7 @@ abstract class Generator{ public abstract function __construct(array $settings = []); - public abstract function init(Level $level, Random $random); + public abstract function init(GenerationChunkManager $level, Random $random); public abstract function generateChunk($chunkX, $chunkZ); diff --git a/src/pocketmine/level/generator/Normal.php b/src/pocketmine/level/generator/Normal.php index c77943760..c790c8937 100644 --- a/src/pocketmine/level/generator/Normal.php +++ b/src/pocketmine/level/generator/Normal.php @@ -37,11 +37,15 @@ use pocketmine\level\generator\populator\Tree; use pocketmine\level\Level; use pocketmine\math\Vector3 as Vector3; use pocketmine\utils\Random; +use pocketmine\level\generator\populator\Populator; class Normal extends Generator{ + /** @var Populator[] */ private $populators = []; + /** @var GenerationChunkManager */ private $level; + /** @var Random */ private $random; private $worldHeight = 65; private $waterHeight = 63; @@ -62,7 +66,7 @@ class Normal extends Generator{ return []; } - public function init(Level $level, Random $random){ + public function init(GenerationChunkManager $level, Random $random){ $this->level = $level; $this->random = $random; $this->random->setSeed($this->level->getSeed()); diff --git a/src/pocketmine/level/generator/object/BigTree.php b/src/pocketmine/level/generator/object/BigTree.php index b7fbad7ca..a96f069d7 100644 --- a/src/pocketmine/level/generator/object/BigTree.php +++ b/src/pocketmine/level/generator/object/BigTree.php @@ -21,6 +21,7 @@ namespace pocketmine\level\generator\object; +use pocketmine\level\ChunkManager; use pocketmine\level\Level; use pocketmine\math\Vector3 as Vector3; @@ -39,11 +40,11 @@ class BigTree extends Tree{ private $addLogVines = false; private $addCocoaPlants = false; - public function canPlaceObject(Level $level, Vector3 $pos){ + public function canPlaceObject(ChunkManager $level, $x, $y, $z){ return false; } - public function placeObject(Level $level, Vector3 $pos, $type){ + public function placeObject(ChunkManager $level, $x, $y, $z, $type){ $this->trunkHeight = (int) ($this->totalHeight * $this->trunkHeightMultiplier); $leaves = $this->getLeafGroupPoints($level, $pos); diff --git a/src/pocketmine/level/generator/object/Ore.php b/src/pocketmine/level/generator/object/Ore.php index 6808e85f2..e55251131 100644 --- a/src/pocketmine/level/generator/object/Ore.php +++ b/src/pocketmine/level/generator/object/Ore.php @@ -21,6 +21,7 @@ namespace pocketmine\level\generator\object; +use pocketmine\level\ChunkManager; use pocketmine\level\Level; use pocketmine\math\Vector3 as Vector3; use pocketmine\math\VectorMath; @@ -39,20 +40,20 @@ class Ore{ return $this->type; } - public function canPlaceObject(Level $level, $x, $y, $z){ - return ($level->level->getBlockID($x, $y, $z) === 1); + public function canPlaceObject(ChunkManager $level, $x, $y, $z){ + return ($level->getBlockIdAt($x, $y, $z) === 1); } - public function placeObject(Level $level, Vector3 $pos){ + public function placeObject(ChunkManager $level, $x, $y, $z){ $clusterSize = (int) $this->type->clusterSize; $angle = $this->random->nextFloat() * M_PI; $offset = VectorMath::getDirection2D($angle)->multiply($clusterSize)->divide(8); - $x1 = $pos->x + 8 + $offset->x; - $x2 = $pos->x + 8 - $offset->x; - $z1 = $pos->z + 8 + $offset->y; - $z2 = $pos->z + 8 - $offset->y; - $y1 = $pos->y + $this->random->nextRange(0, 3) + 2; - $y2 = $pos->y + $this->random->nextRange(0, 3) + 2; + $x1 = $x + 8 + $offset->x; + $x2 = $x + 8 - $offset->x; + $z1 = $z + 8 + $offset->y; + $z2 = $z + 8 - $offset->y; + $y1 = $y + $this->random->nextRange(0, 3) + 2; + $y2 = $y + $this->random->nextRange(0, 3) + 2; for($count = 0; $count <= $clusterSize; ++$count){ $seedX = $x1 + ($x2 - $x1) * $count / $clusterSize; $seedY = $y1 + ($y2 - $y1) * $count / $clusterSize; @@ -80,8 +81,8 @@ class Ore{ $sizeZ = ($z + 0.5 - $seedZ) / $size; $sizeZ *= $sizeZ; - if(($sizeX + $sizeY + $sizeZ) < 1 and $level->level->getBlockID($x, $y, $z) === 1){ - $level->setBlockRaw(new Vector3($x, $y, $z), $this->type->material); + if(($sizeX + $sizeY + $sizeZ) < 1 and $level->getBlockIdAt($x, $y, $z) === 1){ + $level->setBlockIdAt($x, $y, $z, $this->type->material); } } } diff --git a/src/pocketmine/level/generator/object/PineTree.php b/src/pocketmine/level/generator/object/PineTree.php index e85f9823f..9c69efb95 100644 --- a/src/pocketmine/level/generator/object/PineTree.php +++ b/src/pocketmine/level/generator/object/PineTree.php @@ -24,6 +24,8 @@ namespace pocketmine\level\generator\object; use pocketmine\block\Dirt; use pocketmine\block\Leaves; use pocketmine\block\Wood; +use pocketmine\item\Block; +use pocketmine\level\ChunkManager; use pocketmine\level\Level; use pocketmine\math\Vector3 as Vector3; use pocketmine\utils\Random; @@ -34,7 +36,7 @@ class PineTree extends Tree{ private $leavesSizeY = -1; private $leavesAbsoluteMaxRadius = -1; - public function canPlaceObject(Level $level, Vector3 $pos, Random $random){ + public function canPlaceObject(ChunkManager $level, $x, $y, $z, Random $random){ $this->findRandomLeavesSize($random); $checkRadius = 0; for($yy = 0; $yy < $this->totalHeight; ++$yy){ @@ -43,7 +45,7 @@ class PineTree extends Tree{ } for($xx = -$checkRadius; $xx < ($checkRadius + 1); ++$xx){ for($zz = -$checkRadius; $zz < ($checkRadius + 1); ++$zz){ - if(!isset($this->overridable[$level->level->getBlockID($pos->x + $xx, $pos->y + $yy, $pos->z + $zz)])){ + if(!isset($this->overridable[$level->getBlockIdAt($x + $xx, $y + $yy, $z + $zz)])){ return false; } } @@ -59,11 +61,11 @@ class PineTree extends Tree{ $this->leavesAbsoluteMaxRadius = 2 + $random->nextRange(0, 1); } - public function placeObject(Level $level, Vector3 $pos, Random $random){ + public function placeObject(ChunkManager $level, $x, $y, $z, Random $random){ if($this->leavesSizeY === -1 or $this->leavesAbsoluteMaxRadius === -1){ $this->findRandomLeavesSize($random); } - $level->setBlockRaw(new Vector3($pos->x, $pos->y - 1, $pos->z), new Dirt()); + $level->setBlockIdAt($x, $y - 1, $z, Block::DIRT); $leavesRadius = 0; $leavesMaxRadius = 1; $leavesBottomY = $this->totalHeight - $this->leavesSizeY; @@ -73,7 +75,8 @@ class PineTree extends Tree{ for($xx = -$leavesRadius; $xx <= $leavesRadius; ++$xx){ for($zz = -$leavesRadius; $zz <= $leavesRadius; ++$zz){ if(abs($xx) != $leavesRadius or abs($zz) != $leavesRadius or $leavesRadius <= 0){ - $level->setBlockRaw(new Vector3($pos->x + $xx, $pos->y + $yy, $pos->z + $zz), new Leaves($this->type)); + $level->setBlockIdAt($x + $xx, $y + $yy, $z + $zz, Block::LEAVES); + $level->setBlockDataAt($x + $xx, $y + $yy, $z + $zz, $this->type); } } } @@ -89,7 +92,8 @@ class PineTree extends Tree{ } $trunkHeightReducer = $random->nextRange(0, 3); for($yy = 0; $yy < ($this->totalHeight - $trunkHeightReducer); ++$yy){ - $level->setBlockRaw(new Vector3($pos->x, $pos->y + $yy, $pos->z), new Wood($this->type)); + $level->setBlockIdAt($x, $y + $yy, $z, Block::TRUNK); + $level->setBlockDataAt($x, $y + $yy, $z, $this->type); } } diff --git a/src/pocketmine/level/generator/object/Pond.php b/src/pocketmine/level/generator/object/Pond.php index d4697c221..0d027ba73 100644 --- a/src/pocketmine/level/generator/object/Pond.php +++ b/src/pocketmine/level/generator/object/Pond.php @@ -22,6 +22,7 @@ namespace pocketmine\level\generator\object; use pocketmine\block\Block; +use pocketmine\level\ChunkManager; use pocketmine\level\Level; use pocketmine\math\Vector3 as Vector3; use pocketmine\utils\Random; @@ -35,10 +36,10 @@ class Pond{ $this->random = $random; } - public function canPlaceObject(Level $level, Vector3 $pos){ + public function canPlaceObject(ChunkManager $level, Vector3 $pos){ } - public function placeObject(Level $level, Vector3 $pos){ + public function placeObject(ChunkManager $level, Vector3 $pos){ } } \ No newline at end of file diff --git a/src/pocketmine/level/generator/object/SmallTree.php b/src/pocketmine/level/generator/object/SmallTree.php index 911077b5f..34b0d4d96 100644 --- a/src/pocketmine/level/generator/object/SmallTree.php +++ b/src/pocketmine/level/generator/object/SmallTree.php @@ -24,6 +24,8 @@ namespace pocketmine\level\generator\object; use pocketmine\block\Dirt; use pocketmine\block\Leaves; use pocketmine\block\Wood; +use pocketmine\item\Block; +use pocketmine\level\ChunkManager; use pocketmine\level\Level; use pocketmine\math\Vector3 as Vector3; use pocketmine\utils\Random; @@ -38,7 +40,7 @@ class SmallTree extends Tree{ private $addLogVines = false; private $addCocoaPlants = false; - public function canPlaceObject(Level $level, Vector3 $pos, Random $random){ + public function canPlaceObject(ChunkManager $level, $x, $y, $z, Random $random){ $radiusToCheck = 0; for($yy = 0; $yy < $this->trunkHeight + 3; ++$yy){ if($yy == 1 or $yy === $this->trunkHeight){ @@ -46,7 +48,7 @@ class SmallTree extends Tree{ } for($xx = -$radiusToCheck; $xx < ($radiusToCheck + 1); ++$xx){ for($zz = -$radiusToCheck; $zz < ($radiusToCheck + 1); ++$zz){ - if(!isset($this->overridable[$level->level->getBlockID($pos->x + $xx, $pos->y + $yy, $pos->z + $zz)])){ + if(!isset($this->overridable[$level->getBlockIdAt($x + $xx, $y + $yy, $z + $zz)])){ return false; } } @@ -56,10 +58,9 @@ class SmallTree extends Tree{ return true; } - public function placeObject(Level $level, Vector3 $pos, Random $random){ + public function placeObject(ChunkManager $level, $x, $y, $z, Random $random){ // The base dirt block - $dirtpos = new Vector3($pos->x, $pos->y - 1, $pos->z); - $level->setBlockRaw($dirtpos, new Dirt()); + $level->setBlockIdAt($x, $y, $z, Block::DIRT); // Adjust the tree trunk's height randomly // plot [-14:11] int( x / 8 ) + 5 @@ -84,10 +85,8 @@ class SmallTree extends Tree{ for($xx = -$bRadius; $xx <= $bRadius; ++$xx){ for($zz = -$bRadius; $zz <= $bRadius; ++$zz){ if(sqrt(($xx * $xx) + ($zz * $zz)) <= $radius){ - $leafpos = new Vector3($pos->x + $xx, - $pos->y + $yy, - $pos->z + $zz); - $level->setBlockRaw($leafpos, new Leaves($this->type)); + $level->setBlockIdAt($x + $xx, $y + $yy, $z + $zz, Block::LEAVES); + $level->setBlockDataAt($x + $xx, $y + $yy, $z + $zz, $this->type); } } } @@ -96,8 +95,8 @@ class SmallTree extends Tree{ // Place the trunk last if($leaflevel > 1){ - $trunkpos = new Vector3($pos->x, $pos->y + $yy, $pos->z); - $level->setBlockRaw($trunkpos, new Wood($this->type)); + $level->setBlockIdAt($x, $y + $yy, $z, Block::TRUNK); + $level->setBlockDataAt($x, $y + $yy, $z, $this->type); } } } diff --git a/src/pocketmine/level/generator/object/SpruceTree.php b/src/pocketmine/level/generator/object/SpruceTree.php index 803be97c8..a54e60c24 100644 --- a/src/pocketmine/level/generator/object/SpruceTree.php +++ b/src/pocketmine/level/generator/object/SpruceTree.php @@ -24,6 +24,8 @@ namespace pocketmine\level\generator\object; use pocketmine\block\Dirt; use pocketmine\block\Leaves; use pocketmine\block\Wood; +use pocketmine\item\Block; +use pocketmine\level\ChunkManager; use pocketmine\level\Level; use pocketmine\math\Vector3 as Vector3; use pocketmine\utils\Random; @@ -34,7 +36,7 @@ class SpruceTree extends Tree{ private $leavesBottomY = -1; private $leavesMaxRadius = -1; - public function canPlaceObject(Level $level, Vector3 $pos, Random $random){ + public function canPlaceObject(ChunkManager $level, $x, $y, $z, Random $random){ $this->findRandomLeavesSize($random); $checkRadius = 0; for($yy = 0; $yy < $this->totalHeight + 2; ++$yy){ @@ -43,7 +45,7 @@ class SpruceTree extends Tree{ } for($xx = -$checkRadius; $xx < ($checkRadius + 1); ++$xx){ for($zz = -$checkRadius; $zz < ($checkRadius + 1); ++$zz){ - if(!isset($this->overridable[$level->level->getBlockID($pos->x + $xx, $pos->y + $yy, $pos->z + $zz)])){ + if(!isset($this->overridable[$level->getBlockIdAt($x + $xx, $y + $yy, $z + $zz)])){ return false; } } @@ -59,28 +61,30 @@ class SpruceTree extends Tree{ $this->leavesMaxRadius = 1 + $random->nextRange(0, 1); } - public function placeObject(Level $level, Vector3 $pos, Random $random){ + public function placeObject(ChunkManager $level, $x, $y, $z, Random $random){ if($this->leavesBottomY === -1 or $this->leavesMaxRadius === -1){ $this->findRandomLeavesSize($random); } - $level->setBlockRaw(new Vector3($pos->x, $pos->y - 1, $pos->z), new Dirt()); + $level->setBlockIdAt($x, $y - 1, $z, Block::DIRT); $leavesRadius = 0; for($yy = $this->totalHeight; $yy >= $this->leavesBottomY; --$yy){ for($xx = -$leavesRadius; $xx <= $leavesRadius; ++$xx){ for($zz = -$leavesRadius; $zz <= $leavesRadius; ++$zz){ if(abs($xx) != $leavesRadius or abs($zz) != $leavesRadius or $leavesRadius <= 0){ - $level->setBlockRaw(new Vector3($pos->x + $xx, $pos->y + $yy, $pos->z + $zz), new Leaves($this->type)); + $level->setBlockIdAt($x + $xx, $y + $yy, $z + $zz, Block::LEAVES); + $level->setBlockDataAt($x + $xx, $y + $yy, $z + $zz, $this->type); } } } - if($leavesRadius > 0 and $yy === ($pos->y + $this->leavesBottomY + 1)){ + if($leavesRadius > 0 and $yy === ($y + $this->leavesBottomY + 1)){ --$leavesRadius; }elseif($leavesRadius < $this->leavesMaxRadius){ ++$leavesRadius; } } for($yy = 0; $yy < ($this->totalHeight - 1); ++$yy){ - $level->setBlockRaw(new Vector3($pos->x, $pos->y + $yy, $pos->z), new Wood($this->type)); + $level->setBlockIdAt($x, $y + $yy, $z, Block::TRUNK); + $level->setBlockDataAt($x, $y + $yy, $z, $this->type); } } diff --git a/src/pocketmine/level/generator/object/TallGrass.php b/src/pocketmine/level/generator/object/TallGrass.php index a6ac29f8a..aaa0a6ac5 100644 --- a/src/pocketmine/level/generator/object/TallGrass.php +++ b/src/pocketmine/level/generator/object/TallGrass.php @@ -22,27 +22,29 @@ namespace pocketmine\level\generator\object; use pocketmine\block\Block; +use pocketmine\level\ChunkManager; use pocketmine\level\Level; use pocketmine\math\Vector3 as Vector3; use pocketmine\utils\Random; class TallGrass{ - public static function growGrass(Level $level, Vector3 $pos, Random $random, $count = 15, $radius = 10){ - $arr = array( - Block::get(Block::DANDELION, 0), - Block::get(Block::CYAN_FLOWER, 0), - Block::get(Block::TALL_GRASS, 1), - Block::get(Block::TALL_GRASS, 1), - Block::get(Block::TALL_GRASS, 1), - Block::get(Block::TALL_GRASS, 1) - ); + public static function growGrass(ChunkManager $level, Vector3 $pos, Random $random, $count = 15, $radius = 10){ + $arr = [ + [Block::DANDELION, 0], + [Block::CYAN_FLOWER, 0], + [Block::TALL_GRASS, 1], + [Block::TALL_GRASS, 1], + [Block::TALL_GRASS, 1], + [Block::TALL_GRASS, 1] + ]; $arrC = count($arr) - 1; for($c = 0; $c < $count; ++$c){ $x = $random->nextRange($pos->x - $radius, $pos->x + $radius); $z = $random->nextRange($pos->z - $radius, $pos->z + $radius); - if($level->level->getBlockID($x, $pos->y + 1, $z) === Block::AIR and $level->level->getBlockID($x, $pos->y, $z) === Block::GRASS){ + if($level->getBlockIdAt($x, $pos->y + 1, $z) === Block::AIR and $level->getBlockIdAt($x, $pos->y, $z) === Block::GRASS){ $t = $arr[$random->nextRange(0, $arrC)]; - $level->setBlockRaw(new Vector3($x, $pos->y + 1, $z), $t); + $level->setBlockIdAt($x, $pos->y + 1, $z, $t[0]); + $level->setBlockDataAt($x, $pos->y + 1, $z, $t[1]); } } } diff --git a/src/pocketmine/level/generator/object/Tree.php b/src/pocketmine/level/generator/object/Tree.php index f9349c582..26c397aa0 100644 --- a/src/pocketmine/level/generator/object/Tree.php +++ b/src/pocketmine/level/generator/object/Tree.php @@ -22,6 +22,7 @@ namespace pocketmine\level\generator\object; use pocketmine\block\Sapling; +use pocketmine\level\ChunkManager; use pocketmine\level\Level; use pocketmine\math\Vector3 as Vector3; use pocketmine\utils\Random; @@ -36,7 +37,7 @@ class Tree{ 18 => true, ); - public static function growTree(Level $level, Vector3 $pos, Random $random, $type = 0){ + public static function growTree(ChunkManager $level, $x, $y, $z, Random $random, $type = 0){ switch($type & 0x03){ case Sapling::SPRUCE: if($random->nextRange(0, 1) === 1){ @@ -62,8 +63,8 @@ class Tree{ //} break; } - if($tree->canPlaceObject($level, $pos, $random)){ - $tree->placeObject($level, $pos, $random); + if($tree->canPlaceObject($level, $x, $y, $z, $random)){ + $tree->placeObject($level, $x, $y, $z, $random); } } } \ No newline at end of file diff --git a/src/pocketmine/level/generator/populator/Mineshaft.php b/src/pocketmine/level/generator/populator/Mineshaft.php index 6acebb8c2..0f2567ddb 100644 --- a/src/pocketmine/level/generator/populator/Mineshaft.php +++ b/src/pocketmine/level/generator/populator/Mineshaft.php @@ -21,6 +21,7 @@ namespace pocketmine\level\generator\populator; +use pocketmine\level\ChunkManager; use pocketmine\level\Level; use pocketmine\utils\Random; @@ -31,7 +32,7 @@ class Mineshaft extends Populator{ private static $BASE_Y = 35; private static $RAND_Y = 11; - public function populate(Level $level, $chunkX, $chunkZ, Random $random){ + public function populate(ChunkManager $level, $chunkX, $chunkZ, Random $random){ if($random->nextRange(0, self::$ODD) === 0){ //$mineshaft = new Mineshaft($random); } diff --git a/src/pocketmine/level/generator/populator/Ore.php b/src/pocketmine/level/generator/populator/Ore.php index 5959f2f0c..a330bc4b2 100644 --- a/src/pocketmine/level/generator/populator/Ore.php +++ b/src/pocketmine/level/generator/populator/Ore.php @@ -21,6 +21,7 @@ namespace pocketmine\level\generator\populator; +use pocketmine\level\ChunkManager; use pocketmine\level\generator\object\Ore as ObjectOre; use pocketmine\level\Level; use pocketmine\math\Vector3 as Vector3; @@ -29,7 +30,7 @@ use pocketmine\utils\Random; class Ore extends Populator{ private $oreTypes = []; - public function populate(Level $level, $chunkX, $chunkZ, Random $random){ + public function populate(ChunkManager $level, $chunkX, $chunkZ, Random $random){ foreach($this->oreTypes as $type){ $ore = new ObjectOre($random, $type); for($i = 0; $i < $ore->type->clusterCount; ++$i){ @@ -37,7 +38,7 @@ class Ore extends Populator{ $y = $random->nextRange($ore->type->minHeight, $ore->type->maxHeight); $z = $random->nextRange($chunkZ << 4, ($chunkZ << 4) + 15); if($ore->canPlaceObject($level, $x, $y, $z)){ - $ore->placeObject($level, new Vector3($x, $y, $z)); + $ore->placeObject($level, $x, $y, $z); } } } diff --git a/src/pocketmine/level/generator/populator/Pond.php b/src/pocketmine/level/generator/populator/Pond.php index dbc63a22c..b7c7cb1e7 100644 --- a/src/pocketmine/level/generator/populator/Pond.php +++ b/src/pocketmine/level/generator/populator/Pond.php @@ -22,6 +22,7 @@ namespace pocketmine\level\generator\populator; use pocketmine\block\Water; +use pocketmine\level\ChunkManager; use pocketmine\level\Level; use pocketmine\math\Vector3 as Vector3; use pocketmine\utils\Random; @@ -31,16 +32,14 @@ class Pond extends Populator{ private $lavaOdd = 4; private $lavaSurfaceOdd = 4; - public function populate(Level $level, $chunkX, $chunkZ, Random $random){ + public function populate(ChunkManager $level, $chunkX, $chunkZ, Random $random){ if($random->nextRange(0, $this->waterOdd) === 0){ - $v = new Vector3( - $random->nextRange($chunkX << 4, ($chunkX << 4) + 16), - $random->nextRange(0, 128), - $random->nextRange($chunkZ << 4, ($chunkZ << 4) + 16) - ); + $x = $random->nextRange($chunkX << 4, ($chunkX << 4) + 16); + $y = $random->nextRange(0, 128); + $z = $random->nextRange($chunkZ << 4, ($chunkZ << 4) + 16); $pond = new \pocketmine\level\generator\object\Pond($random, new Water()); - if($pond->canPlaceObject($level, $v)){ - $pond->placeObject($level, $v); + if($pond->canPlaceObject($level, $x, $y, $z)){ + $pond->placeObject($level, $x, $y, $z); } } } diff --git a/src/pocketmine/level/generator/populator/Populator.php b/src/pocketmine/level/generator/populator/Populator.php index fa44c8e63..0d58e40e5 100644 --- a/src/pocketmine/level/generator/populator/Populator.php +++ b/src/pocketmine/level/generator/populator/Populator.php @@ -24,9 +24,10 @@ */ namespace pocketmine\level\generator\populator; +use pocketmine\level\ChunkManager; use pocketmine\level\Level; use pocketmine\utils\Random; abstract class Populator{ - public abstract function populate(Level $level, $chunkX, $chunkZ, Random $random); + public abstract function populate(ChunkManager $level, $chunkX, $chunkZ, Random $random); } \ No newline at end of file diff --git a/src/pocketmine/level/generator/populator/TallGrass.php b/src/pocketmine/level/generator/populator/TallGrass.php index 17b6b3254..a327020c5 100644 --- a/src/pocketmine/level/generator/populator/TallGrass.php +++ b/src/pocketmine/level/generator/populator/TallGrass.php @@ -23,12 +23,13 @@ namespace pocketmine\level\generator\populator; use pocketmine\block\Block; use pocketmine\block\TallGrass as BlockTallGrass; +use pocketmine\level\ChunkManager; use pocketmine\level\Level; use pocketmine\math\Vector3 as Vector3; use pocketmine\utils\Random; class TallGrass extends Populator{ - /** @var Level */ + /** @var ChunkManager */ private $level; private $randomAmount; private $baseAmount; @@ -41,7 +42,7 @@ class TallGrass extends Populator{ $this->baseAmount = $amount; } - public function populate(Level $level, $chunkX, $chunkZ, Random $random){ + public function populate(ChunkManager $level, $chunkX, $chunkZ, Random $random){ $this->level = $level; $amount = $random->nextRange(0, $this->randomAmount + 1) + $this->baseAmount; for($i = 0; $i < $amount; ++$i){ @@ -51,22 +52,23 @@ class TallGrass extends Populator{ $xx = $x - 7 + $random->nextRange(0, 15); $zz = $z - 7 + $random->nextRange(0, 15); $yy = $this->getHighestWorkableBlock($xx, $zz); - $vector = new Vector3($xx, $yy, $zz); - if($yy !== -1 and $this->canTallGrassStay($this->level->getBlockRaw($vector))){ - $this->level->setBlockRaw($vector, new BlockTallGrass(1)); + + if($yy !== -1 and $this->canTallGrassStay($xx, $yy, $zz)){ + $this->level->setBlockIdAt($xx, $yy, $zz, Block::TALL_GRASS); + $this->level->setBlockDataAt($xx, $yy, $zz, 1); } } } } - private function canTallGrassStay(Block $block){ - return $block->getID() === Block::AIR and $block->getSide(0)->getID() === Block::GRASS; + private function canTallGrassStay($x, $y, $z){ + return $this->level->getBlockIdAt($x, $y, $z) === Block::AIR and $this->level->getBlockIdAt($x, $y - 1, $z) === Block::GRASS; } private function getHighestWorkableBlock($x, $z){ for($y = 128; $y > 0; --$y){ - $b = $this->level->getBlockRaw(new Vector3($x, $y, $z)); - if($b->getID() === Block::AIR or $b->getID() === Block::LEAVES){ + $b = $this->level->getBlockIdAt($x, $y, $z); + if($b === Block::AIR or $b === Block::LEAVES){ if(--$y <= 0){ return -1; } diff --git a/src/pocketmine/level/generator/populator/Tree.php b/src/pocketmine/level/generator/populator/Tree.php index 6369faf38..70a5923f7 100644 --- a/src/pocketmine/level/generator/populator/Tree.php +++ b/src/pocketmine/level/generator/populator/Tree.php @@ -23,12 +23,14 @@ namespace pocketmine\level\generator\populator; use pocketmine\block\Block; use pocketmine\block\Sapling; +use pocketmine\level\ChunkManager; use pocketmine\level\generator\object\Tree as ObjectTree; use pocketmine\level\Level; use pocketmine\math\Vector3 as Vector3; use pocketmine\utils\Random; class Tree extends Populator{ + /** @var ChunkManager */ private $level; private $randomAmount; private $baseAmount; @@ -41,7 +43,7 @@ class Tree extends Populator{ $this->baseAmount = $amount; } - public function populate(Level $level, $chunkX, $chunkZ, Random $random){ + public function populate(ChunkManager $level, $chunkX, $chunkZ, Random $random){ $this->level = $level; $amount = $random->nextRange(0, $this->randomAmount + 1) + $this->baseAmount; for($i = 0; $i < $amount; ++$i){ @@ -56,14 +58,14 @@ class Tree extends Populator{ }else{ $meta = Sapling::OAK; } - ObjectTree::growTree($this->level, new Vector3($x, $y, $z), $random, $meta); + ObjectTree::growTree($this->level, $x, $y, $z, $random, $meta); } } private function getHighestWorkableBlock($x, $z){ for($y = 128; $y > 0; --$y){ - $b = $this->level->getBlockRaw(new Vector3($x, $y, $z)); - if($b->getID() !== Block::DIRT and $b->getID() !== Block::GRASS){ + $b = $this->level->getBlockIdAt($x, $y, $z); + if($b !== Block::DIRT and $b !== Block::GRASS){ if(--$y <= 0){ return -1; } diff --git a/src/pocketmine/tile/Chest.php b/src/pocketmine/tile/Chest.php index 37dcbcb56..b04c7d9ca 100644 --- a/src/pocketmine/tile/Chest.php +++ b/src/pocketmine/tile/Chest.php @@ -25,6 +25,7 @@ use pocketmine\inventory\ChestInventory; use pocketmine\inventory\DoubleChestInventory; use pocketmine\inventory\InventoryHolder; use pocketmine\item\Item; +use pocketmine\level\format\Chunk; use pocketmine\level\Level; use pocketmine\math\Vector3 as Vector3; use pocketmine\nbt\NBT; @@ -44,9 +45,9 @@ class Chest extends Spawnable implements InventoryHolder, Container{ /** @var DoubleChestInventory */ protected $doubleInventory = null; - public function __construct(Level $level, Compound $nbt){ + public function __construct(Chunk $chunk, Compound $nbt){ $nbt["id"] = Tile::CHEST; - parent::__construct($level, $nbt); + parent::__construct($chunk, $nbt); $this->inventory = new ChestInventory($this); for($i = 0; $i < $this->getSize(); ++$i){ $this->inventory->setItem($i, $this->getItem($i)); diff --git a/src/pocketmine/tile/Container.php b/src/pocketmine/tile/Container.php index 75270751e..1b5c4909f 100644 --- a/src/pocketmine/tile/Container.php +++ b/src/pocketmine/tile/Container.php @@ -26,9 +26,22 @@ use pocketmine\item\Item; use pocketmine\Network; interface Container{ + + /** + * @param int $index + * + * @return Item + */ public function getItem($index); + /** + * @param int $index + * @param Item $item + */ public function setItem($index, Item $item); + /** + * @return int + */ public function getSize(); } diff --git a/src/pocketmine/tile/Furnace.php b/src/pocketmine/tile/Furnace.php index 314df9047..0a60f710e 100644 --- a/src/pocketmine/tile/Furnace.php +++ b/src/pocketmine/tile/Furnace.php @@ -26,6 +26,7 @@ use pocketmine\inventory\FurnaceInventory; use pocketmine\inventory\FurnaceRecipe; use pocketmine\inventory\InventoryHolder; use pocketmine\item\Item; +use pocketmine\level\format\Chunk; use pocketmine\level\Level; use pocketmine\nbt\NBT; use pocketmine\nbt\tag\Byte; @@ -37,9 +38,9 @@ class Furnace extends Tile implements InventoryHolder, Container{ /** @var FurnaceInventory */ protected $inventory; - public function __construct(Level $level, Compound $nbt){ + public function __construct(Chunk $chunk, Compound $nbt){ $nbt["id"] = Tile::FURNACE; - parent::__construct($level, $nbt); + parent::__construct($chunk, $nbt); $this->inventory = new FurnaceInventory($this); for($i = 0; $i < $this->getSize(); ++$i){ $this->inventory->setItem($i, $this->getItem($i)); diff --git a/src/pocketmine/tile/Sign.php b/src/pocketmine/tile/Sign.php index 76b5fe0ec..aad1924e7 100644 --- a/src/pocketmine/tile/Sign.php +++ b/src/pocketmine/tile/Sign.php @@ -21,6 +21,7 @@ namespace pocketmine\tile; +use pocketmine\level\format\Chunk; use pocketmine\level\Level; use pocketmine\nbt\NBT; use pocketmine\nbt\tag\Compound; @@ -31,9 +32,9 @@ use pocketmine\Player; class Sign extends Spawnable{ - public function __construct(Level $level, Compound $nbt){ + public function __construct(Chunk $chunk, Compound $nbt){ $nbt["id"] = Tile::SIGN; - parent::__construct($level, $nbt); + parent::__construct($chunk, $nbt); } public function setText($line1 = "", $line2 = "", $line3 = "", $line4 = ""){ @@ -42,7 +43,6 @@ class Sign extends Spawnable{ $this->namedtag->Text3 = $line3; $this->namedtag->Text4 = $line4; $this->spawnToAll(); - $this->server->handle("tile.update", $this); return true; } diff --git a/src/pocketmine/tile/Spawnable.php b/src/pocketmine/tile/Spawnable.php index 4c6fa11cc..2a6bf2cd5 100644 --- a/src/pocketmine/tile/Spawnable.php +++ b/src/pocketmine/tile/Spawnable.php @@ -21,6 +21,7 @@ namespace pocketmine\tile; +use pocketmine\level\format\Chunk; use pocketmine\level\Level; use pocketmine\nbt\tag\Compound; use pocketmine\Player; @@ -28,8 +29,8 @@ use pocketmine\Player; abstract class Spawnable extends Tile{ public abstract function spawnTo(Player $player); - public function __construct(Level $level, Compound $nbt){ - parent::__construct($level, $nbt); + public function __construct(Chunk $chunk, Compound $nbt){ + parent::__construct($chunk, $nbt); $this->spawnToAll(); } diff --git a/src/pocketmine/tile/Tile.php b/src/pocketmine/tile/Tile.php index eb453c7d1..c56354fd4 100644 --- a/src/pocketmine/tile/Tile.php +++ b/src/pocketmine/tile/Tile.php @@ -25,11 +25,10 @@ */ namespace pocketmine\tile; -use pocketmine\level\format\pmf\LevelFormat; +use pocketmine\level\format\Chunk; use pocketmine\level\Level; use pocketmine\level\Position; use pocketmine\nbt\tag\Compound; -use pocketmine\Server; abstract class Tile extends Position{ const SIGN = "Sign"; @@ -45,7 +44,8 @@ abstract class Tile extends Position{ */ public static $needUpdate = []; - public $chunkIndex; + /** @var Chunk */ + public $chunk; public $name; public $id; public $x; @@ -63,9 +63,10 @@ abstract class Tile extends Position{ } - public function __construct(Level $level, Compound $nbt){ - $this->server = Server::getInstance(); - $this->setLevel($level, true); //Strong reference + public function __construct(Chunk $chunk, Compound $nbt){ + $this->server = $chunk->getLevel()->getLevel()->getServer(); + $this->chunk = $chunk; + $this->setLevel($chunk->getLevel()->getLevel(), true); //Strong reference $this->namedtag = $nbt; $this->closed = false; $this->name = ""; @@ -75,10 +76,8 @@ abstract class Tile extends Position{ $this->y = (int) $this->namedtag["y"]; $this->z = (int) $this->namedtag["z"]; - $index = LevelFormat::getIndex($this->x >> 4, $this->z >> 4); - $this->chunkIndex = $index; + $this->chunk->addTile($this); $this->getLevel()->addTile($this); - $this->getLevel()->chunkTiles[$this->chunkIndex][$this->id] = $this; } public function saveNBT(){ @@ -100,7 +99,7 @@ abstract class Tile extends Position{ $this->closed = true; unset(Tile::$needUpdate[$this->id]); $this->getLevel()->removeTile($this); - unset($this->getLevel()->chunkTiles[$this->chunkIndex][$this->id]); + $this->chunk->removeTile($this); } } diff --git a/src/raklib b/src/raklib index 7f6711a75..345009779 160000 --- a/src/raklib +++ b/src/raklib @@ -1 +1 @@ -Subproject commit 7f6711a75ef0e64537cdeb302013386dd2b99c5f +Subproject commit 345009779b039fa9c0238fd0a57df5eeff2a2753