PocketMine-MP  1.4 - API 1.10.0
 All Classes Namespaces Functions Variables Pages
Level.php
1 <?php
2 
3 /*
4  *
5  * ____ _ _ __ __ _ __ __ ____
6  * | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
7  * | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
8  * | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
9  * |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
10  *
11  * This program is free software: you can redistribute it and/or modify
12  * it under the terms of the GNU Lesser General Public License as published by
13  * the Free Software Foundation, either version 3 of the License, or
14  * (at your option) any later version.
15  *
16  * @author PocketMine Team
17  * @link http://www.pocketmine.net/
18  *
19  *
20 */
21 
25 namespace pocketmine\level;
26 
49 use pocketmine\entity\Item as DroppedItem;
98 
99 #include <rules/Level.h>
100 
101 class Level implements ChunkManager, Metadatable{
102 
103  private static $levelIdCounter = 1;
104  public static $COMPRESSION_LEVEL = 8;
105 
106 
107  const BLOCK_UPDATE_NORMAL = 1;
108  const BLOCK_UPDATE_RANDOM = 2;
109  const BLOCK_UPDATE_SCHEDULED = 3;
110  const BLOCK_UPDATE_WEAK = 4;
111  const BLOCK_UPDATE_TOUCH = 5;
112 
113  const TIME_DAY = 0;
114  const TIME_SUNSET = 12000;
115  const TIME_NIGHT = 14000;
116  const TIME_SUNRISE = 23000;
117 
118  const TIME_FULL = 24000;
119 
121  protected $tiles = [];
122 
124  protected $players = [];
125 
127  protected $entities = [];
128 
130  public $updateEntities = [];
132  public $updateTiles = [];
133 
134  protected $blockCache = [];
135 
137  protected $server;
138 
140  protected $levelId;
141 
143  protected $provider;
144 
146  protected $usedChunks = [];
147 
149  protected $unloadQueue;
150 
151  protected $time;
152  public $stopTime;
153 
154  private $folderName;
155 
157  private $chunks = [];
158 
160  protected $changedBlocks = [];
161  protected $changedCount = [];
162 
164  private $updateQueue;
165  private $updateQueueIndex = [];
166 
168  private $chunkSendQueue = [];
169  private $chunkSendTasks = [];
170 
171  private $chunkGenerationQueue = [];
172 
173  private $autoSave = true;
174 
176  private $blockMetadata;
177 
178  private $useSections;
179  private $blockOrder;
180 
182  private $temporalPosition;
184  private $temporalVector;
185 
187  private $blockStates;
188 
189  protected $chunkTickRadius;
190  protected $chunkTickList = [];
191  protected $chunksPerTick;
192  protected $clearChunksOnTick;
193  protected $randomTickBlocks = [
194  Block::GRASS => Grass::class,
195  Block::SAPLING => Sapling::class,
196  Block::LEAVES => Leaves::class,
197  Block::WHEAT_BLOCK => Wheat::class,
198  Block::FARMLAND => Farmland::class,
199  Block::SNOW_LAYER => SnowLayer::class,
200  Block::ICE => Ice::class,
201  Block::CACTUS => Cactus::class,
202  Block::SUGARCANE_BLOCK => Sugarcane::class,
203  Block::RED_MUSHROOM => RedMushroom::class,
204  Block::BROWN_MUSHROOM => BrownMushroom::class,
205  Block::PUMPKIN_STEM => PumpkinStem::class,
206  Block::MELON_STEM => MelonStem::class,
207  //Block::VINE => true,
208  Block::MYCELIUM => Mycelium::class,
209  //Block::COCOA_BLOCK => true,
210  Block::CARROT_BLOCK => Carrot::class,
211  Block::POTATO_BLOCK => Potato::class,
212  Block::LEAVES2 => Leaves2::class,
213 
214  Block::BEETROOT_BLOCK => Beetroot::class,
215  ];
216 
218  public $timings;
219 
220  protected $generator;
221 
230  public static function chunkHash($x, $z){
231  return PHP_INT_SIZE === 8 ? (($x & 0xFFFFFFFF) << 32) | ($z & 0xFFFFFFFF) : $x . ":" . $z;
232  }
233 
234  public static function blockHash($x, $y, $z){
235  return PHP_INT_SIZE === 8 ? (($x & 0xFFFFFFF) << 35) | (($y & 0x7f) << 28) | ($z & 0xFFFFFFF) : $x . ":" . $y .":". $z;
236  }
237 
238  public static function getBlockXYZ($hash, &$x, &$y, &$z){
239  if(PHP_INT_SIZE === 8){
240  $x = ($hash >> 35) << 36 >> 36;
241  $y = (($hash >> 28) & 0x7f);// << 57 >> 57; //it's always positive
242  $z = ($hash & 0xFFFFFFF) << 36 >> 36;
243  }else{
244  $hash = explode(":", $hash);
245  $x = (int) $hash[0];
246  $y = (int) $hash[1];
247  $z = (int) $hash[2];
248  }
249  }
250 
251  public static function getXZ($hash, &$x, &$z){
252  if(PHP_INT_SIZE === 8){
253  $x = ($hash >> 32) << 32 >> 32;
254  $z = ($hash & 0xFFFFFFFF) << 32 >> 32;
255  }else{
256  $hash = explode(":", $hash);
257  $x = (int) $hash[0];
258  $z = (int) $hash[1];
259  }
260  }
261 
272  public function __construct(Server $server, $name, $path, $provider){
273  $this->blockStates = Block::$fullList;
274  $this->levelId = static::$levelIdCounter++;
275  $this->blockMetadata = new BlockMetadataStore($this);
276  $this->server = $server;
277  $this->autoSave = $server->getAutoSave();
278 
281  if(is_subclass_of($provider, LevelProvider::class, true)){
282  $this->provider = new $provider($this, $path);
283  }else{
284  throw new LevelException("Provider is not a subclass of LevelProvider");
285  }
286  $this->server->getLogger()->info("Preparing level \"" . $this->provider->getName() . "\"");
287  $this->generator = Generator::getGenerator($this->provider->getGenerator());
288 
289  $this->blockOrder = $provider::getProviderOrder();
290  $this->useSections = $provider::usesChunkSection();
291 
292  $this->folderName = $name;
293  $this->updateQueue = new ReversePriorityQueue();
294  $this->updateQueue->setExtractFlags(\SplPriorityQueue::EXTR_BOTH);
295  $this->time = (int) $this->provider->getTime();
296 
297  $this->chunkTickRadius = min($this->server->getViewDistance(), max(1, (int) $this->server->getProperty("chunk-ticking.tick-radius", 4)));
298  $this->chunksPerTick = (int) $this->server->getProperty("chunk-ticking.per-tick", 260);
299  $this->chunkTickList = [];
300  $this->clearChunksOnTick = (bool) $this->server->getProperty("chunk-ticking.clear-tick-list", false);
301 
302  $this->timings = new LevelTimings($this);
303  $this->temporalPosition = new Position(0, 0, 0, $this);
304  $this->temporalVector = new Vector3(0, 0, 0);
305  }
306 
307  public function initLevel(){
308  $this->server->getGenerationManager()->openLevel($this, $this->generator, $this->provider->getGeneratorOptions());
309  }
310 
314  public function getBlockMetadata(){
315  return $this->blockMetadata;
316  }
317 
321  public function getServer(){
322  return $this->server;
323  }
324 
328  final public function getProvider(){
329  return $this->provider;
330  }
331 
337  final public function getId(){
338  return $this->levelId;
339  }
340 
341  public function close(){
342 
343  if($this->getAutoSave()){
344  $this->save();
345  }
346 
347  foreach($this->chunks as $chunk){
348  $this->unloadChunk($chunk->getX(), $chunk->getZ(), false);
349  }
350 
351  $this->server->getGenerationManager()->closeLevel($this);
352  $this->provider->close();
353  $this->provider = null;
354  $this->blockMetadata = null;
355  $this->blockCache = [];
356  $this->temporalPosition = null;
357  }
358 
362  public function getAutoSave(){
363  return $this->autoSave === true;
364  }
365 
369  public function setAutoSave($value){
370  $this->autoSave = $value;
371  }
372 
380  public function unload($force = false){
381 
382  $ev = new LevelUnloadEvent($this);
383 
384  if($this === $this->server->getDefaultLevel() and $force !== true){
385  $ev->setCancelled(true);
386  }
387 
388  $this->server->getPluginManager()->callEvent($ev);
389 
390  if(!$force and $ev->isCancelled()){
391  return false;
392  }
393 
394  $this->server->getLogger()->info("Unloading level \"" . $this->getName() . "\"");
395  $defaultLevel = $this->server->getDefaultLevel();
396  foreach($this->getPlayers() as $player){
397  if($this === $defaultLevel or $defaultLevel === null){
398  $player->close(TextFormat::YELLOW . $player->getName() . " has left the game", "Forced default level unload");
399  }elseif($defaultLevel instanceof Level){
400  $player->teleport($this->server->getDefaultLevel()->getSafeSpawn());
401  }
402  }
403 
404  if($this === $defaultLevel){
405  $this->server->setDefaultLevel(null);
406  }
407 
408  $this->close();
409 
410  return true;
411  }
412 
421  public function getUsingChunk($X, $Z){
422  return isset($this->usedChunks[$index = Level::chunkHash($X, $Z)]) ? $this->usedChunks[$index] : [];
423  }
424 
433  public function useChunk($X, $Z, Player $player){
434  $index = Level::chunkHash($X, $Z);
435  $this->loadChunk($X, $Z);
436  $this->usedChunks[$index][$player->getId()] = $player;
437  }
438 
447  public function freeChunk($X, $Z, Player $player){
448  unset($this->usedChunks[$index = Level::chunkHash($X, $Z)][$player->getId()]);
449  $this->unloadChunkRequest($X, $Z, true);
450  }
451 
456  public function checkTime(){
457  if($this->stopTime == true){
458  return;
459  }else{
460  $this->time += 1.25;
461  }
462  }
463 
468  public function sendTime(){
469  $pk = new SetTimePacket();
470  $pk->time = (int) $this->time;
471  $pk->started = $this->stopTime == false;
472 
473  Server::broadcastPacket($this->players, $pk);
474  }
475 
484  public function doTick($currentTick){
485 
486  $this->timings->doTick->startTiming();
487 
488  $this->checkTime();
489 
490  if(($currentTick % 200) === 0){
491  $this->sendTime();
492  }
493 
494  $this->unloadChunks();
495 
496  $X = null;
497  $Z = null;
498 
499  //Do block updates
500  $this->timings->doTickPending->startTiming();
501  while($this->updateQueue->count() > 0 and $this->updateQueue->current()["priority"] <= $currentTick){
502  $block = $this->getBlock($this->updateQueue->extract()["data"]);
503  unset($this->updateQueueIndex[Level::blockHash($block->x, $block->y, $block->z)]);
504  $block->onUpdate(self::BLOCK_UPDATE_SCHEDULED);
505  }
506  $this->timings->doTickPending->stopTiming();
507 
508  $this->timings->entityTick->startTiming();
509  //Update entities that need update
510  Timings::$tickEntityTimer->startTiming();
511  foreach($this->updateEntities as $id => $entity){
512  if($entity->closed or !$entity->onUpdate($currentTick)){
513  unset($this->updateEntities[$id]);
514  }
515  }
516  Timings::$tickEntityTimer->stopTiming();
517  $this->timings->entityTick->stopTiming();
518 
519  $this->timings->tileEntityTick->startTiming();
520  //Update tiles that need update
521  if(count($this->updateTiles) > 0){
522  //Timings::$tickTileEntityTimer->startTiming();
523  foreach($this->updateTiles as $id => $tile){
524  if($tile->onUpdate() !== true){
525  unset($this->updateTiles[$id]);
526  }
527  }
528  //Timings::$tickTileEntityTimer->stopTiming();
529  }
530  $this->timings->tileEntityTick->stopTiming();
531 
532  $this->timings->doTickTiles->startTiming();
533  $this->tickChunks();
534  $this->timings->doTickTiles->stopTiming();
535 
536  if(count($this->changedCount) > 0){
537  if(count($this->players) > 0){
538  foreach($this->changedCount as $index => $mini){
539  for($Y = 0; $Y < 8; ++$Y){
540  if(($mini & (1 << $Y)) === 0){
541  continue;
542  }
543  if(count($this->changedBlocks[$index][$Y]) < 256){
544  continue;
545  }else{
546  $X = null;
547  $Z = null;
548  Level::getXZ($index, $X, $Z);
549  foreach($this->getUsingChunk($X, $Z) as $p){
550  $p->unloadChunk($X, $Z);
551  }
552  unset($this->changedBlocks[$index][$Y]);
553  }
554  }
555  }
556  $this->changedCount = [];
557  if(count($this->changedBlocks) > 0){
558  foreach($this->changedBlocks as $index => $mini){
559  foreach($mini as $blocks){
561  foreach($blocks as $b){
562  $pk = new UpdateBlockPacket();
563  $pk->x = $b->x;
564  $pk->y = $b->y;
565  $pk->z = $b->z;
566  $pk->block = $b->getId();
567  $pk->meta = $b->getDamage();
568  Server::broadcastPacket($this->getUsingChunk($b->x >> 4, $b->z >> 4), $pk);
569  }
570  }
571  }
572  $this->changedBlocks = [];
573  }
574  }else{
575  $this->changedCount = [];
576  $this->changedBlocks = [];
577  }
578 
579  }
580 
581  $this->processChunkRequest();
582 
583  $this->timings->doTick->stopTiming();
584  }
585 
586  public function clearCache(){
587  $this->blockCache = [];
588  }
589 
590  private function tickChunks(){
591  if($this->chunksPerTick <= 0 or count($this->players) === 0){
592  $this->chunkTickList = [];
593  return;
594  }
595 
596  $chunksPerPlayer = min(200, max(1, (int) ((($this->chunksPerTick - count($this->players)) / count($this->players)) + 0.5)));
597  $randRange = 3 + $chunksPerPlayer / 30;
598  $randRange = $randRange > $this->chunkTickRadius ? $this->chunkTickRadius : $randRange;
599 
600  foreach($this->players as $player){
601  $x = $player->x >> 4;
602  $z = $player->z >> 4;
603 
604  $index = Level::chunkHash($x, $z);
605  $existingPlayers = max(0, isset($this->chunkTickList[$index]) ? $this->chunkTickList[$index] : 0);
606  $this->chunkTickList[$index] = $existingPlayers + 1;
607  for($chunk = 0; $chunk < $chunksPerPlayer; ++$chunk){
608  $dx = mt_rand(-$randRange, $randRange);
609  $dz = mt_rand(-$randRange, $randRange);
610  $hash = Level::chunkHash($dx + $x, $dz + $z);
611  if(!isset($this->chunkTickList[$hash]) and $this->isChunkLoaded($dx + $x, $dz + $z)){
612  $this->chunkTickList[$hash] = -1;
613  }
614  }
615  }
616 
617  $chunkX = $chunkZ = null;
618 
619  foreach($this->chunkTickList as $index => $players){
620  Level::getXZ($index, $chunkX, $chunkZ);
621 
622  if(!$this->isChunkLoaded($chunkX, $chunkZ) or isset($this->unloadQueue[$index]) and $players > 0){
623  unset($this->chunkTickList[$index]);
624  continue;
625  }
626  $chunk = $this->getChunk($chunkX, $chunkZ, true);
627 
628  foreach($chunk->getEntities() as $entity){
629  $entity->scheduleUpdate();
630  }
631 
632 
633  if($this->useSections){
634  foreach($chunk->getSections() as $section){
635  if(!($section instanceof EmptyChunkSection)){
636  $Y = $section->getY();
637  $k = mt_rand(0, PHP_INT_MAX);
638  for($i = 0; $i < 3; ++$i){
639  $j = $k >> 2;
640  $x = $j & 0x0f;
641  $y = ($j >> 8) & 0x0f;
642  $z = ($j >> 16) & 0x0f;
643  $k %= 1073741827;
644  $blockId = $section->getBlockId($x, $y, $z);
645  if(isset($this->randomTickBlocks[$blockId])){
646  $class = $this->randomTickBlocks[$blockId];
648  $block = new $class($section->getBlockData($x, $y, $z));
649  $block->x = $chunkX * 16 + $x;
650  $block->y = ($Y << 4) + $y;
651  $block->z = $chunkZ * 16 + $z;
652  $block->level = $this;
653  $block->onUpdate(self::BLOCK_UPDATE_RANDOM);
654  }
655  }
656  }
657  }
658  }else{
659  for($Y = 0; $Y < 8; ++$Y){
660  $k = mt_rand(0, PHP_INT_MAX);
661  for($i = 0; $i < 3; ++$i){
662  $j = $k >> 2;
663  $x = $j & 0x0f;
664  $y = ($j >> 8) & 0x0f;
665  $z = ($j >> 16) & 0x0f;
666  $k %= 1073741827;
667  $blockId = $chunk->getBlockId($x, $y + ($Y << 4), $z);
668  if(isset($this->randomTickBlocks[$blockId])){
669  $class = $this->randomTickBlocks[$blockId];
671  $block = new $class($chunk->getBlockData($x, $y + ($Y << 4), $z));
672  $block->x = $chunkX * 16 + $x;
673  $block->y = ($Y << 4) + $y;
674  $block->z = $chunkZ * 16 + $z;
675  $block->level = $this;
676  $block->onUpdate(self::BLOCK_UPDATE_RANDOM);
677  }
678  }
679  }
680  }
681  }
682 
683  if($this->clearChunksOnTick){
684  $this->chunkTickList = [];
685  }
686  }
687 
688  public function __debugInfo(){
689  return [];
690  }
691 
697  public function save($force = false){
698 
699  if($this->getAutoSave() === false and $force === false){
700  return false;
701  }
702 
703  $this->server->getPluginManager()->callEvent(new LevelSaveEvent($this));
704 
705  $this->provider->setTime((int) $this->time);
706  $this->saveChunks();
707  if($this->provider instanceof BaseLevelProvider){
708  $this->provider->saveLevelData();
709  }
710 
711  return true;
712  }
713 
714  public function saveChunks(){
715  foreach($this->chunks as $chunk){
716  if($chunk->hasChanged()){
717  $this->provider->setChunk($chunk->getX(), $chunk->getZ(), $chunk);
718  $this->provider->saveChunk($chunk->getX(), $chunk->getZ());
719  $chunk->setChanged(false);
720  }
721  }
722  }
723 
727  public function updateAround(Vector3 $pos){
728  $this->server->getPluginManager()->callEvent($ev = new BlockUpdateEvent($this->getBlock($this->temporalVector->setComponents($pos->x - 1, $pos->y, $pos->z))));
729  if(!$ev->isCancelled()){
730  $ev->getBlock()->onUpdate(self::BLOCK_UPDATE_NORMAL);
731  }
732 
733  $this->server->getPluginManager()->callEvent($ev = new BlockUpdateEvent($this->getBlock($this->temporalVector->setComponents($pos->x + 1, $pos->y, $pos->z))));
734  if(!$ev->isCancelled()){
735  $ev->getBlock()->onUpdate(self::BLOCK_UPDATE_NORMAL);
736  }
737 
738  $this->server->getPluginManager()->callEvent($ev = new BlockUpdateEvent($this->getBlock($this->temporalVector->setComponents($pos->x, $pos->y - 1, $pos->z))));
739  if(!$ev->isCancelled()){
740  $ev->getBlock()->onUpdate(self::BLOCK_UPDATE_NORMAL);
741  }
742 
743  $this->server->getPluginManager()->callEvent($ev = new BlockUpdateEvent($this->getBlock($this->temporalVector->setComponents($pos->x, $pos->y + 1, $pos->z))));
744  if(!$ev->isCancelled()){
745  $ev->getBlock()->onUpdate(self::BLOCK_UPDATE_NORMAL);
746  }
747 
748  $this->server->getPluginManager()->callEvent($ev = new BlockUpdateEvent($this->getBlock($this->temporalVector->setComponents($pos->x, $pos->y, $pos->z - 1))));
749  if(!$ev->isCancelled()){
750  $ev->getBlock()->onUpdate(self::BLOCK_UPDATE_NORMAL);
751  }
752 
753  $this->server->getPluginManager()->callEvent($ev = new BlockUpdateEvent($this->getBlock($this->temporalVector->setComponents($pos->x, $pos->y, $pos->z + 1))));
754  if(!$ev->isCancelled()){
755  $ev->getBlock()->onUpdate(self::BLOCK_UPDATE_NORMAL);
756  }
757  }
758 
763  public function scheduleUpdate(Vector3 $pos, $delay){
764  if(isset($this->updateQueueIndex[$index = Level::blockHash($pos->x, $pos->y, $pos->z)]) and $this->updateQueueIndex[$index] <= $delay){
765  return;
766  }
767  $this->updateQueueIndex[$index] = $delay;
768  $this->updateQueue->insert(new Vector3((int) $pos->x, (int) $pos->y, (int) $pos->z), (int) $delay + $this->server->getTick());
769  }
770 
776  public function getCollisionBlocks(AxisAlignedBB $bb){
777  $minX = Math::floorFloat($bb->minX);
778  $minY = Math::floorFloat($bb->minY);
779  $minZ = Math::floorFloat($bb->minZ);
780  $maxX = Math::floorFloat($bb->maxX + 1);
781  $maxY = Math::floorFloat($bb->maxY + 1);
782  $maxZ = Math::floorFloat($bb->maxZ + 1);
783 
784  $collides = [];
785 
786  $v = $this->temporalVector;
787 
788  for($v->z = $minZ; $v->z < $maxZ; ++$v->z){
789  for($v->x = $minX; $v->x < $maxX; ++$v->x){
790  for($v->y = $minY - 1; $v->y < $maxY; ++$v->y){
791  $block = $this->getBlock($v);
792  if($block->getId() !== 0){
793  $block->collidesWithBB($bb, $collides);
794  }
795  }
796  }
797  }
798 
799  return $collides;
800  }
801 
807  public function isFullBlock(Vector3 $pos){
808  if($pos instanceof Block){
809  $bb = $pos->getBoundingBox();
810  }else{
811  $bb = $this->getBlock($pos)->getBoundingBox();
812  }
813 
814  return $bb !== null and $bb->getAverageEdgeLength() >= 1;
815  }
816 
824  public function getCollisionCubes(Entity $entity, AxisAlignedBB $bb, $entities = true){
825  $minX = Math::floorFloat($bb->minX);
826  $minY = Math::floorFloat($bb->minY);
827  $minZ = Math::floorFloat($bb->minZ);
828  $maxX = Math::floorFloat($bb->maxX + 1);
829  $maxY = Math::floorFloat($bb->maxY + 1);
830  $maxZ = Math::floorFloat($bb->maxZ + 1);
831 
832  $collides = [];
833  $v = $this->temporalVector;
834 
835  for($v->z = $minZ; $v->z < $maxZ; ++$v->z){
836  for($v->x = $minX; $v->x < $maxX; ++$v->x){
837  for($v->y = $minY - 1; $v->y < $maxY; ++$v->y){
838  $block = $this->getBlock($v);
839  if($block->getId() !== 0){
840  $block->collidesWithBB($bb, $collides);
841  }
842  }
843  }
844  }
845 
846  if($entities){
847  foreach($this->getCollidingEntities($bb->grow(0.25, 0.25, 0.25), $entity) as $ent){
848  $collides[] = clone $ent->boundingBox;
849  }
850  }
851 
852  return $collides;
853  }
854 
855  /*
856  public function rayTraceBlocks(Vector3 $pos1, Vector3 $pos2, $flag = false, $flag1 = false, $flag2 = false){
857  if(!is_nan($pos1->x) and !is_nan($pos1->y) and !is_nan($pos1->z)){
858  if(!is_nan($pos2->x) and !is_nan($pos2->y) and !is_nan($pos2->z)){
859  $x1 = (int) $pos1->x;
860  $y1 = (int) $pos1->y;
861  $z1 = (int) $pos1->z;
862  $x2 = (int) $pos2->x;
863  $y2 = (int) $pos2->y;
864  $z2 = (int) $pos2->z;
865 
866  $block = $this->getBlock(Vector3::createVector($x1, $y1, $z1));
867 
868  if(!$flag1 or $block->getBoundingBox() !== null){
869  $ob = $block->calculateIntercept($pos1, $pos2);
870  if($ob !== null){
871  return $ob;
872  }
873  }
874 
875  $movingObjectPosition = null;
876 
877  $k = 200;
878 
879  while($k-- >= 0){
880  if(is_nan($pos1->x) or is_nan($pos1->y) or is_nan($pos1->z)){
881  return null;
882  }
883 
884  if($x1 === $x2 and $y1 === $y2 and $z1 === $z2){
885  return $flag2 ? $movingObjectPosition : null;
886  }
887 
888  $flag3 = true;
889  $flag4 = true;
890  $flag5 = true;
891 
892  $i = 999;
893  $j = 999;
894  $k = 999;
895 
896  if($x1 > $x2){
897  $i = $x2 + 1;
898  }elseif($x1 < $x2){
899  $i = $x2;
900  }else{
901  $flag3 = false;
902  }
903 
904  if($y1 > $y2){
905  $j = $y2 + 1;
906  }elseif($y1 < $y2){
907  $j = $y2;
908  }else{
909  $flag4 = false;
910  }
911 
912  if($z1 > $z2){
913  $k = $z2 + 1;
914  }elseif($z1 < $z2){
915  $k = $z2;
916  }else{
917  $flag5 = false;
918  }
919 
920  //TODO
921  }
922  }
923  }
924  }
925  */
926 
927  public function getFullLight(Vector3 $pos){
928  $chunk = $this->getChunk($pos->x >> 4, $pos->z >> 4, false);
929  $level = 0;
930  if($chunk instanceof FullChunk){
931  $level = $chunk->getBlockSkyLight($pos->x & 0x0f, $pos->y & 0x7f, $pos->z & 0x0f);
932  //TODO: decrease light level by time of day
933  if($level < 15){
934  $level = max($chunk->getBlockLight($pos->x & 0x0f, $pos->y & 0x7f, $pos->z & 0x0f));
935  }
936  }
937 
938  return $level;
939  }
940 
948  public function getFullBlock($x, $y, $z){
949  return $this->getChunk($x >> 4, $z >> 4, false)->getFullBlock($x & 0x0f, $y & 0x7f, $z & 0x0f);
950  }
951 
960  public function getBlock(Vector3 $pos, $cached = true){
961  $index = Level::blockHash($pos->x, $pos->y, $pos->z);
962  if($cached === true and isset($this->blockCache[$index])){
963  return $this->blockCache[$index];
964  }elseif($pos->y >= 0 and $pos->y < 128 and isset($this->chunks[$chunkIndex = Level::chunkHash($pos->x >> 4, $pos->z >> 4)])){
965  $fullState = $this->chunks[$chunkIndex]->getFullBlock($pos->x & 0x0f, $pos->y & 0x7f, $pos->z & 0x0f);
966  }else{
967  $fullState = 0;
968  }
969 
970  $block = clone $this->blockStates[$fullState & 0xfff];
971 
972  $block->x = $pos->x;
973  $block->y = $pos->y;
974  $block->z = $pos->z;
975  $block->level = $this;
976 
977  return $this->blockCache[$index] = $block;
978  }
979 
980  public function updateAllLight(Vector3 $pos){
981  $this->updateBlockSkyLight($pos->x, $pos->y, $pos->z);
982  $this->updateBlockLight($pos->x, $pos->y, $pos->z);
983  }
984 
985  public function updateBlockSkyLight($x, $y, $z){
986  //TODO
987  }
988 
989  public function updateBlockLight($x, $y, $z){
990  $lightPropagationQueue = new \SplQueue();
991  $lightRemovalQueue = new \SplQueue();
992  $visited = [];
993  $removalVisited = [];
994 
995  $oldLevel = $this->getBlockLightAt($x, $y, $z);
996  $newLevel = (int) Block::$light[$this->getBlockIdAt($x, $y, $z)];
997 
998  if($oldLevel !== $newLevel){
999  $this->setBlockLightAt($x, $y, $z, $newLevel);
1000 
1001  if($newLevel < $oldLevel){
1002  $removalVisited[Level::blockHash($x, $y, $z)] = true;
1003  $lightRemovalQueue->enqueue([new Vector3($x, $y, $z), $oldLevel]);
1004  }else{
1005  $visited[Level::blockHash($x, $y, $z)] = true;
1006  $lightPropagationQueue->enqueue(new Vector3($x, $y, $z));
1007  }
1008  }
1009 
1010  while(!$lightRemovalQueue->isEmpty()){
1012  $val = $lightRemovalQueue->dequeue();
1013  $node = $val[0];
1014  $lightLevel = $val[1];
1015 
1016  $this->computeRemoveBlockLight($node->x - 1, $node->y, $node->z, $lightLevel, $lightRemovalQueue, $lightPropagationQueue, $removalVisited, $visited);
1017  $this->computeRemoveBlockLight($node->x + 1, $node->y, $node->z, $lightLevel, $lightRemovalQueue, $lightPropagationQueue, $removalVisited, $visited);
1018  $this->computeRemoveBlockLight($node->x, $node->y - 1, $node->z, $lightLevel, $lightRemovalQueue, $lightPropagationQueue, $removalVisited, $visited);
1019  $this->computeRemoveBlockLight($node->x, $node->y + 1, $node->z, $lightLevel, $lightRemovalQueue, $lightPropagationQueue, $removalVisited, $visited);
1020  $this->computeRemoveBlockLight($node->x, $node->y, $node->z - 1, $lightLevel, $lightRemovalQueue, $lightPropagationQueue, $removalVisited, $visited);
1021  $this->computeRemoveBlockLight($node->x, $node->y, $node->z + 1, $lightLevel, $lightRemovalQueue, $lightPropagationQueue, $removalVisited, $visited);
1022  }
1023 
1024  while(!$lightPropagationQueue->isEmpty()){
1026  $node = $lightPropagationQueue->dequeue();
1027 
1028  $lightLevel = $this->getBlockLightAt($node->x, $node->y, $node->z) - (int) Block::$lightFilter[$this->getBlockIdAt($node->x, $node->y, $node->z)];
1029 
1030  if($lightLevel >= 1){
1031  $this->computeSpreadBlockLight($node->x - 1, $node->y, $node->z, $lightLevel, $lightPropagationQueue, $visited);
1032  $this->computeSpreadBlockLight($node->x + 1, $node->y, $node->z, $lightLevel, $lightPropagationQueue, $visited);
1033  $this->computeSpreadBlockLight($node->x, $node->y - 1, $node->z, $lightLevel, $lightPropagationQueue, $visited);
1034  $this->computeSpreadBlockLight($node->x, $node->y + 1, $node->z, $lightLevel, $lightPropagationQueue, $visited);
1035  $this->computeSpreadBlockLight($node->x, $node->y, $node->z - 1, $lightLevel, $lightPropagationQueue, $visited);
1036  $this->computeSpreadBlockLight($node->x, $node->y, $node->z + 1, $lightLevel, $lightPropagationQueue, $visited);
1037  }
1038  }
1039  }
1040 
1041  private function computeRemoveBlockLight($x, $y, $z, $currentLight, \SplQueue $queue, \SplQueue $spreadQueue, array &$visited, array &$spreadVisited){
1042  $current = $this->getBlockLightAt($x, $y, $z);
1043 
1044  if($current !== 0 and $current < $currentLight){
1045  $this->setBlockLightAt($x, $y, $z, 0);
1046 
1047  if(!isset($visited[$index = Level::blockHash($x, $y, $z)])){
1048  $visited[$index] = true;
1049  if($current > 1){
1050  $queue->enqueue([new Vector3($x, $y, $z), $current]);
1051  }
1052  }
1053  }elseif($current >= $currentLight){
1054  if(!isset($spreadVisited[$index = Level::blockHash($x, $y, $z)])){
1055  $spreadVisited[$index] = true;
1056  $spreadQueue->enqueue(new Vector3($x, $y, $z));
1057  }
1058  }
1059  }
1060 
1061  private function computeSpreadBlockLight($x, $y, $z, $currentLight, \SplQueue $queue, array &$visited){
1062  $current = $this->getBlockLightAt($x, $y, $z);
1063 
1064  if($current < $currentLight){
1065  $this->setBlockLightAt($x, $y, $z, $currentLight);
1066 
1067  if(!isset($visited[$index = Level::blockHash($x, $y, $z)])){
1068  $visited[$index] = true;
1069  if($currentLight > 1){
1070  $queue->enqueue(new Vector3($x, $y, $z));
1071  }
1072  }
1073  }
1074  }
1075 
1094  public function setBlock(Vector3 $pos, Block $block, $direct = false, $update = true){
1095  if($pos->y < 0 or $pos->y >= 128){
1096  return false;
1097  }
1098 
1099  unset($this->blockCache[$index = Level::blockHash($pos->x, $pos->y, $pos->z)]);
1100 
1101  if($this->getChunk($pos->x >> 4, $pos->z >> 4, true)->setBlock($pos->x & 0x0f, $pos->y & 0x7f, $pos->z & 0x0f, $block->getId(), $block->getDamage())){
1102  if(!($pos instanceof Position)){
1103  $pos = $this->temporalPosition->setComponents($pos->x, $pos->y, $pos->z);
1104  }
1105  $block->position($pos);
1106  $index = Level::chunkHash($pos->x >> 4, $pos->z >> 4);
1107  if(ADVANCED_CACHE == true){
1108  Cache::remove("world:" . $this->getId() . ":" . $index);
1109  }
1110 
1111  if($direct === true){
1112  $pk = new UpdateBlockPacket();
1113  $pk->x = $pos->x;
1114  $pk->y = $pos->y;
1115  $pk->z = $pos->z;
1116  $pk->block = $block->getId();
1117  $pk->meta = $block->getDamage();
1118 
1119  Server::broadcastPacket($this->getUsingChunk($pos->x >> 4, $pos->z >> 4), $pk);
1120  }else{
1121  if(!($pos instanceof Position)){
1122  $pos = $this->temporalPosition->setComponents($pos->x, $pos->y, $pos->z);
1123  }
1124  $block->position($pos);
1125  if(!isset($this->changedBlocks[$index])){
1126  $this->changedBlocks[$index] = [];
1127  $this->changedCount[$index] = 0;
1128  }
1129  $Y = $pos->y >> 4;
1130  if(!isset($this->changedBlocks[$index][$Y])){
1131  $this->changedBlocks[$index][$Y] = [];
1132  $this->changedCount[$index] |= 1 << $Y;
1133  }
1134  $this->changedBlocks[$index][$Y][] = clone $block;
1135  }
1136 
1137  if($update === true){
1138  $this->updateAllLight($block);
1139 
1140  $this->server->getPluginManager()->callEvent($ev = new BlockUpdateEvent($block));
1141  if(!$ev->isCancelled()){
1142  $ev->getBlock()->onUpdate(self::BLOCK_UPDATE_NORMAL);
1143  foreach($this->getNearbyEntities(new AxisAlignedBB($block->x - 1, $block->y - 1, $block->z - 1, $block->x + 2, $block->y + 2, $block->z + 2)) as $entity){
1144  $entity->scheduleUpdate();
1145  }
1146  }
1147 
1148  $this->updateAround($pos);
1149  }
1150  }
1151  }
1152 
1159  public function dropItem(Vector3 $source, Item $item, Vector3 $motion = null, $delay = 10){
1160  $motion = $motion === null ? new Vector3(lcg_value() * 0.2 - 0.1, 0.2, lcg_value() * 0.2 - 0.1) : $motion;
1161  if($item->getId() > 0 and $item->getCount() > 0){
1162  $itemEntity = Entity::createEntity("Item", $this->getChunk($source->getX() >> 4, $source->getZ() >> 4), new Compound("", [
1163  "Pos" => new Enum("Pos", [
1164  new Double("", $source->getX()),
1165  new Double("", $source->getY()),
1166  new Double("", $source->getZ())
1167  ]),
1168 
1169  "Motion" => new Enum("Motion", [
1170  new Double("", $motion->x),
1171  new Double("", $motion->y),
1172  new Double("", $motion->z)
1173  ]),
1174  "Rotation" => new Enum("Rotation", [
1175  new Float("", lcg_value() * 360),
1176  new Float("", 0)
1177  ]),
1178  "Health" => new Short("Health", 5),
1179  "Item" => new Compound("Item", [
1180  "id" => new Short("id", $item->getId()),
1181  "Damage" => new Short("Damage", $item->getDamage()),
1182  "Count" => new Byte("Count", $item->getCount())
1183  ]),
1184  "PickupDelay" => new Short("PickupDelay", $delay)
1185  ]));
1186 
1187  $itemEntity->spawnToAll();
1188  }
1189  }
1190 
1201  public function useBreakOn(Vector3 $vector, Item &$item = null, Player $player = null){
1202  $target = $this->getBlock($vector);
1203  //TODO: Adventure mode checks
1204 
1205  if($item === null){
1206  $item = Item::get(Item::AIR, 0, 0);
1207  }
1208 
1209  if($player instanceof Player){
1210  $ev = new BlockBreakEvent($player, $target, $item, ($player->getGamemode() & 0x01) === 1 ? true : false);
1211 
1212  if($player->isSurvival() and $item instanceof Item and !$target->isBreakable($item)){
1213  $ev->setCancelled();
1214  }
1215 
1216  if(!$player->isOp() and ($distance = $this->server->getConfigInt("spawn-protection", 16)) > -1){
1217  $t = new Vector2($target->x, $target->z);
1218  $s = new Vector2($this->getSpawnLocation()->x, $this->getSpawnLocation()->z);
1219  if($t->distance($s) <= $distance){ //set it to cancelled so plugins can bypass this
1220  $ev->setCancelled();
1221  }
1222  }
1223  $this->server->getPluginManager()->callEvent($ev);
1224  if($ev->isCancelled()){
1225  return false;
1226  }
1227 
1228  $breakTime = $player->isCreative() ? 0.15 : $target->getBreakTime($item);
1229 
1230  if(!$ev->getInstaBreak() and ($player->lastBreak + $breakTime) >= microtime(true)){
1231  return false;
1232  }
1233 
1234  $player->lastBreak = microtime(true);
1235  }elseif($item instanceof Item and !$target->isBreakable($item)){
1236  return false;
1237  }
1238 
1239  $level = $target->getLevel();
1240 
1241  if($level instanceof Level){
1242  $above = $level->getBlock(new Vector3($target->x, $target->y + 1, $target->z));
1243  if($above instanceof Block){
1244  if($above->getId() === Item::FIRE){
1245  $level->setBlock($above, new Air(), true);
1246  }
1247  }
1248  }
1249  $drops = $target->getDrops($item); //Fixes tile entities being deleted before getting drops
1250  $target->onBreak($item);
1251  $tile = $this->getTile($target);
1252  if($tile instanceof Tile){
1253  if($tile instanceof InventoryHolder){
1254  if($tile instanceof Chest){
1255  $tile->unpair();
1256  }
1257 
1258  foreach($tile->getInventory()->getContents() as $chestItem){
1259  $this->dropItem($target, $chestItem);
1260  }
1261  }
1262 
1263  $tile->close();
1264  }
1265 
1266  if($item instanceof Item){
1267  $item->useOn($target);
1268  if($item->isTool() and $item->getDamage() >= $item->getMaxDurability()){
1269  $item = Item::get(Item::AIR, 0, 0);
1270  }
1271  }
1272 
1273  if(!($player instanceof Player) or $player->isSurvival()){
1274  foreach($drops as $drop){
1275  if($drop[2] > 0){
1276  $this->dropItem($vector->add(0.5, 0.5, 0.5), Item::get(...$drop));
1277  }
1278  }
1279  }
1280 
1281  return true;
1282  }
1283 
1297  public function useItemOn(Vector3 $vector, Item &$item, $face, $fx = 0.0, $fy = 0.0, $fz = 0.0, Player $player = null){
1298  $target = $this->getBlock($vector);
1299  $block = $target->getSide($face);
1300 
1301  if($block->y > 127 or $block->y < 0){
1302  return false;
1303  }
1304 
1305  if($target->getId() === Item::AIR){
1306  return false;
1307  }
1308 
1309  if($player instanceof Player){
1310  $ev = new PlayerInteractEvent($player, $item, $target, $face);
1311  if(!$player->isOp() and ($distance = $this->server->getConfigInt("spawn-protection", 16)) > -1){
1312  $t = new Vector2($target->x, $target->z);
1313  $s = new Vector2($this->getSpawnLocation()->x, $this->getSpawnLocation()->z);
1314  if($t->distance($s) <= $distance){ //set it to cancelled so plugins can bypass this
1315  $ev->setCancelled();
1316  }
1317  }
1318  $this->server->getPluginManager()->callEvent($ev);
1319  if(!$ev->isCancelled()){
1320  $target->onUpdate(self::BLOCK_UPDATE_TOUCH);
1321  if($target->canBeActivated() === true and $target->onActivate($item, $player) === true){
1322  return true;
1323  }
1324 
1325  if($item->canBeActivated() and $item->onActivate($this, $player, $block, $target, $face, $fx, $fy, $fz)){
1326  if($item->getCount() <= 0){
1327  $item = Item::get(Item::AIR, 0, 0);
1328 
1329  return true;
1330  }
1331  }
1332  }
1333  }elseif($target->canBeActivated() === true and $target->onActivate($item, $player) === true){
1334  return true;
1335  }
1336 
1337  if($item->isPlaceable()){
1338  $hand = $item->getBlock();
1339  $hand->position($block);
1340  }elseif($block->getId() === Item::FIRE){
1341  $this->setBlock($block, new Air(), true);
1342 
1343  return false;
1344  }else{
1345  return false;
1346  }
1347 
1348  if(!($block->canBeReplaced() === true or ($hand->getId() === Item::SLAB and $block->getId() === Item::SLAB))){
1349  return false;
1350  }
1351 
1352  if($target->canBeReplaced() === true){
1353  $block = $target;
1354  $hand->position($block);
1355  //$face = -1;
1356  }
1357 
1358  if($hand->isSolid() === true and $hand->getBoundingBox() !== null){
1359  $entities = $this->getCollidingEntities($hand->getBoundingBox());
1360  $realCount = 0;
1361  foreach($entities as $e){
1362  if($e instanceof Arrow or $e instanceof DroppedItem){
1363  continue;
1364  }
1365  ++$realCount;
1366  }
1367 
1368  if($realCount > 0){
1369  return false; //Entity in block
1370  }
1371  }
1372 
1373 
1374  if($player instanceof Player){
1375  $ev = new BlockPlaceEvent($player, $hand, $block, $target, $item);
1376  if(!$player->isOp() and ($distance = $this->server->getConfigInt("spawn-protection", 16)) > -1){
1377  $t = new Vector2($target->x, $target->z);
1378  $s = new Vector2($this->getSpawnLocation()->x, $this->getSpawnLocation()->z);
1379  if($t->distance($s) <= $distance){ //set it to cancelled so plugins can bypass this
1380  $ev->setCancelled();
1381  }
1382  }
1383  $this->server->getPluginManager()->callEvent($ev);
1384  if($ev->isCancelled()){
1385  return false;
1386  }
1387  }
1388 
1389  if($hand->place($item, $block, $target, $face, $fx, $fy, $fz, $player) === false){
1390  return false;
1391  }
1392 
1393  if($hand->getId() === Item::SIGN_POST or $hand->getId() === Item::WALL_SIGN){
1394  $tile = Tile::createTile("Sign", $this->getChunk($block->x >> 4, $block->z >> 4), new Compound(false, [
1395  "id" => new String("id", Tile::SIGN),
1396  "x" => new Int("x", $block->x),
1397  "y" => new Int("y", $block->y),
1398  "z" => new Int("z", $block->z),
1399  "Text1" => new String("Text1", ""),
1400  "Text2" => new String("Text2", ""),
1401  "Text3" => new String("Text3", ""),
1402  "Text4" => new String("Text4", "")
1403  ]));
1404  if($player instanceof Player){
1405  $tile->namedtag->Creator = new String("Creator", $player->getName());
1406  }
1407  }
1408  $item->setCount($item->getCount() - 1);
1409  if($item->getCount() <= 0){
1410  $item = Item::get(Item::AIR, 0, 0);
1411  }
1412 
1413  return true;
1414  }
1415 
1421  public function getEntity($entityId){
1422  return isset($this->entities[$entityId]) ? $this->entities[$entityId] : null;
1423  }
1424 
1430  public function getEntities(){
1431  return $this->entities;
1432  }
1433 
1442  public function getCollidingEntities(AxisAlignedBB $bb, Entity $entity = null){
1443  $nearby = [];
1444 
1445  if($entity === null or $entity->canCollide){
1446  $minX = Math::floorFloat(($bb->minX - 2) / 16);
1447  $maxX = Math::floorFloat(($bb->maxX + 2) / 16);
1448  $minZ = Math::floorFloat(($bb->minZ - 2) / 16);
1449  $maxZ = Math::floorFloat(($bb->maxZ + 2) / 16);
1450 
1451  for($x = $minX; $x <= $maxX; ++$x){
1452  for($z = $minZ; $z <= $maxZ; ++$z){
1453  foreach($this->getChunkEntities($x, $z) as $ent){
1454  if($ent !== $entity and ($entity === null or $entity->canCollideWith($ent)) and $ent->boundingBox->intersectsWith($bb)){
1455  $nearby[] = $ent;
1456  }
1457  }
1458  }
1459  }
1460  }
1461 
1462  return $nearby;
1463  }
1464 
1473  public function getNearbyEntities(AxisAlignedBB $bb, Entity $entity = null){
1474  $nearby = [];
1475 
1476  $minX = Math::floorFloat(($bb->minX - 2) / 16);
1477  $maxX = Math::floorFloat(($bb->maxX + 2) / 16);
1478  $minZ = Math::floorFloat(($bb->minZ - 2) / 16);
1479  $maxZ = Math::floorFloat(($bb->maxZ + 2) / 16);
1480 
1481  for($x = $minX; $x <= $maxX; ++$x){
1482  for($z = $minZ; $z <= $maxZ; ++$z){
1483  foreach($this->getChunkEntities($x, $z) as $ent){
1484  if($ent !== $entity and $ent->boundingBox->intersectsWith($bb)){
1485  $nearby[] = $ent;
1486  }
1487  }
1488  }
1489  }
1490 
1491  return $nearby;
1492  }
1493 
1499  public function getTiles(){
1500  return $this->tiles;
1501  }
1502 
1508  public function getTileById($tileId){
1509  return isset($this->tiles[$tileId]) ? $this->tiles[$tileId] : null;
1510  }
1511 
1517  public function getPlayers(){
1518  return $this->players;
1519  }
1520 
1528  public function getTile(Vector3 $pos){
1529  $chunk = $this->getChunk($pos->x >> 4, $pos->z >> 4, false);
1530 
1531  if($chunk !== null){
1532  return $chunk->getTile($pos->x & 0x0f, $pos->y & 0xff, $pos->z & 0x0f);
1533  }
1534 
1535  return null;
1536  }
1537 
1546  public function getChunkEntities($X, $Z){
1547  return ($chunk = $this->getChunk($X, $Z)) !== null ? $chunk->getEntities() : [];
1548  }
1549 
1558  public function getChunkTiles($X, $Z){
1559  return ($chunk = $this->getChunk($X, $Z)) !== null ? $chunk->getTiles() : [];
1560  }
1561 
1571  public function getBlockIdAt($x, $y, $z){
1572  return $this->getChunk($x >> 4, $z >> 4, true)->getBlockId($x & 0x0f, $y & 0x7f, $z & 0x0f);
1573  }
1574 
1583  public function setBlockIdAt($x, $y, $z, $id){
1584  unset($this->blockCache[Level::blockHash($x, $y, $z)]);
1585  $this->getChunk($x >> 4, $z >> 4, true)->setBlockId($x & 0x0f, $y & 0x7f, $z & 0x0f, $id & 0xff);
1586  }
1587 
1597  public function getBlockDataAt($x, $y, $z){
1598  return $this->getChunk($x >> 4, $z >> 4, true)->getBlockData($x & 0x0f, $y & 0x7f, $z & 0x0f);
1599  }
1600 
1609  public function setBlockDataAt($x, $y, $z, $data){
1610  unset($this->blockCache[Level::blockHash($x, $y, $z)]);
1611  $this->getChunk($x >> 4, $z >> 4, true)->setBlockData($x & 0x0f, $y & 0x7f, $z & 0x0f, $data & 0x0f);
1612  }
1613 
1623  public function getBlockSkyLightAt($x, $y, $z){
1624  return $this->getChunk($x >> 4, $z >> 4, true)->getBlockSkyLight($x & 0x0f, $y & 0x7f, $z & 0x0f);
1625  }
1626 
1635  public function setBlockSkyLightAt($x, $y, $z, $level){
1636  $this->getChunk($x >> 4, $z >> 4, true)->setBlockSkyLight($x & 0x0f, $y & 0x7f, $z & 0x0f, $level & 0x0f);
1637  }
1638 
1648  public function getBlockLightAt($x, $y, $z){
1649  return $this->getChunk($x >> 4, $z >> 4, true)->getBlockLight($x & 0x0f, $y & 0x7f, $z & 0x0f);
1650  }
1651 
1660  public function setBlockLightAt($x, $y, $z, $level){
1661  $this->getChunk($x >> 4, $z >> 4, true)->setBlockLight($x & 0x0f, $y & 0x7f, $z & 0x0f, $level & 0x0f);
1662  }
1663 
1670  public function getBiomeId($x, $z){
1671  return $this->getChunk($x >> 4, $z >> 4, true)->getBiomeId($x & 0x0f, $z & 0x0f);
1672  }
1673 
1680  public function getBiomeColor($x, $z){
1681  return $this->getChunk($x >> 4, $z >> 4, true)->getBiomeColor($x & 0x0f, $z & 0x0f);
1682  }
1683 
1690  public function getHeightMap($x, $z){
1691  return $this->getChunk($x >> 4, $z >> 4, true)->getHeightMap($x & 0x0f, $z & 0x0f);
1692  }
1693 
1699  public function setBiomeId($x, $z, $biomeId){
1700  $this->getChunk($x >> 4, $z >> 4, true)->setBiomeId($x & 0x0f, $z & 0x0f, $biomeId);
1701  }
1702 
1710  public function setBiomeColor($x, $z, $R, $G, $B){
1711  $this->getChunk($x >> 4, $z >> 4, true)->setBiomeColor($x & 0x0f, $z & 0x0f, $R, $G, $B);
1712  }
1713 
1719  public function setHeightMap($x, $z, $value){
1720  $this->getChunk($x >> 4, $z >> 4, true)->setHeightMap($x & 0x0f, $z & 0x0f, $value);
1721  }
1722 
1732  public function getChunk($x, $z, $create = false){
1733  if(isset($this->chunks[$index = Level::chunkHash($x, $z)])){
1734  return $this->chunks[$index];
1735  }elseif($this->loadChunk($x, $z, $create) and $this->chunks[$index] !== null){
1736  return $this->chunks[$index];
1737  }
1738 
1739  return null;
1740  }
1741 
1751  public function getChunkAt($x, $z, $create = false){
1752  return $this->getChunk($x, $z, $create);
1753  }
1754 
1755  public function generateChunkCallback($x, $z, FullChunk $chunk){
1756  $oldChunk = $this->getChunk($x, $z, false);
1757  unset($this->chunkGenerationQueue[Level::chunkHash($x, $z)]);
1758  $chunk->setProvider($this->provider);
1759  $this->setChunk($x, $z, $chunk);
1760  $chunk = $this->getChunk($x, $z, false);
1761  if($chunk !== null and ($oldChunk === null or $oldChunk->isPopulated() === false) and $chunk->isPopulated()){
1762  $this->server->getPluginManager()->callEvent(new ChunkPopulateEvent($chunk));
1763  }
1764  }
1765 
1766  public function setChunk($x, $z, FullChunk $chunk, $unload = true){
1767  $index = Level::chunkHash($x, $z);
1768  if($unload){
1769  foreach($this->getUsingChunk($x, $z) as $player){
1770  $player->unloadChunk($x, $z);
1771  }
1772  $this->provider->setChunk($x, $z, $chunk);
1773  $this->chunks[$index] = $chunk;
1774  }else{
1775  $this->provider->setChunk($x, $z, $chunk);
1776  $this->chunks[$index] = $chunk;
1777  }
1778  if(ADVANCED_CACHE == true){
1779  Cache::remove("world:" . $this->getId() . ":" . Level::chunkHash($x, $z));
1780  }
1781  $chunk->setChanged();
1782  }
1783 
1792  public function getHighestBlockAt($x, $z){
1793  return $this->getChunk($x >> 4, $z >> 4, true)->getHighestBlockAt($x & 0x0f, $z & 0x0f);
1794  }
1795 
1802  public function isChunkLoaded($x, $z){
1803  return isset($this->chunks[Level::chunkHash($x, $z)]) or $this->provider->isChunkLoaded($x, $z);
1804  }
1805 
1812  public function isChunkGenerated($x, $z){
1813  $chunk = $this->getChunk($x, $z);
1814  return $chunk !== null ? $chunk->isGenerated() : false;
1815  }
1816 
1823  public function isChunkPopulated($x, $z){
1824  $chunk = $this->getChunk($x, $z);
1825  return $chunk !== null ? $chunk->isPopulated() : false;
1826  }
1827 
1833  public function getSpawnLocation(){
1834  return Position::fromObject($this->provider->getSpawn(), $this);
1835  }
1836 
1842  public function setSpawnLocation(Vector3 $pos){
1843  $previousSpawn = $this->getSpawnLocation();
1844  $this->provider->setSpawn($pos);
1845  $this->server->getPluginManager()->callEvent(new SpawnChangeEvent($this, $previousSpawn));
1846  }
1847 
1848  public function requestChunk($x, $z, Player $player, $order = LevelProvider::ORDER_ZXY){
1849  $index = Level::chunkHash($x, $z);
1850  if(!isset($this->chunkSendQueue[$index])){
1851  $this->chunkSendQueue[$index] = [];
1852  }
1853 
1854  $this->chunkSendQueue[$index][spl_object_hash($player)] = $player;
1855  }
1856 
1857  protected function processChunkRequest(){
1858  if(count($this->chunkSendQueue) > 0){
1859  $this->timings->syncChunkSendTimer->startTiming();
1860 
1861  $x = null;
1862  $z = null;
1863  foreach($this->chunkSendQueue as $index => $players){
1864  if(isset($this->chunkSendTasks[$index])){
1865  continue;
1866  }
1867  Level::getXZ($index, $x, $z);
1868  if(ADVANCED_CACHE == true and ($cache = Cache::get("world:" . $this->getId() . ":" . $index)) !== false){
1870  foreach($players as $player){
1871  if($player->isConnected() and isset($player->usedChunks[$index])){
1872  $player->sendChunk($x, $z, $cache);
1873  }
1874  }
1875  unset($this->chunkSendQueue[$index]);
1876  }else{
1877  $this->chunkSendTasks[$index] = true;
1878  $this->timings->syncChunkSendPrepareTimer->startTiming();
1879  $task = $this->provider->requestChunkTask($x, $z);
1880  if($task instanceof AsyncTask){
1881  $this->server->getScheduler()->scheduleAsyncTask($task);
1882  }
1883  $this->timings->syncChunkSendPrepareTimer->stopTiming();
1884  }
1885  }
1886 
1887  $this->timings->syncChunkSendTimer->stopTiming();
1888  }
1889  }
1890 
1891  public function chunkRequestCallback($x, $z, $payload){
1892  $index = Level::chunkHash($x, $z);
1893  if(isset($this->chunkSendTasks[$index])){
1894 
1895  if(ADVANCED_CACHE == true){
1896  Cache::add("world:" . $this->getId() . ":" . $index, $payload, 60);
1897  }
1898  foreach($this->chunkSendQueue[$index] as $player){
1900  if($player->isConnected() and isset($player->usedChunks[$index])){
1901  $player->sendChunk($x, $z, $payload);
1902  }
1903  }
1904  unset($this->chunkSendQueue[$index]);
1905  unset($this->chunkSendTasks[$index]);
1906  }
1907  }
1908 
1916  public function removeEntity(Entity $entity){
1917  if($entity->getLevel() !== $this){
1918  throw new LevelException("Invalid Entity level");
1919  }
1920 
1921  if($entity instanceof Player){
1922  unset($this->players[$entity->getId()]);
1923  //$this->everyoneSleeping();
1924  }else{
1925  $entity->kill();
1926  }
1927 
1928  unset($this->entities[$entity->getId()]);
1929  unset($this->updateEntities[$entity->getId()]);
1930  }
1931 
1937  public function addEntity(Entity $entity){
1938  if($entity->getLevel() !== $this){
1939  throw new LevelException("Invalid Entity level");
1940  }
1941  if($entity instanceof Player){
1942  $this->players[$entity->getId()] = $entity;
1943  }
1944  $this->entities[$entity->getId()] = $entity;
1945  }
1946 
1952  public function addTile(Tile $tile){
1953  if($tile->getLevel() !== $this){
1954  throw new LevelException("Invalid Tile level");
1955  }
1956  $this->tiles[$tile->getId()] = $tile;
1957  }
1958 
1964  public function removeTile(Tile $tile){
1965  if($tile->getLevel() !== $this){
1966  throw new LevelException("Invalid Tile level");
1967  }
1968 
1969  unset($this->tiles[$tile->getId()]);
1970  unset($this->updateTiles[$tile->getId()]);
1971  }
1972 
1979  public function isChunkInUse($x, $z){
1980  return isset($this->usedChunks[Level::chunkHash($x, $z)]) and count($this->usedChunks[Level::chunkHash($x, $z)]) > 0;
1981  }
1982 
1990  public function loadChunk($x, $z, $generate = true){
1991  if(isset($this->chunks[$index = Level::chunkHash($x, $z)])){
1992  return true;
1993  }
1994 
1995  $this->cancelUnloadChunkRequest($x, $z);
1996 
1997  $chunk = $this->provider->getChunk($x, $z, $generate);
1998  if($chunk !== null){
1999  $this->chunks[$index] = $chunk;
2000  $chunk->initChunk();
2001  }else{
2002  $this->timings->syncChunkLoadTimer->startTiming();
2003  $this->provider->loadChunk($x, $z, $generate);
2004  $this->timings->syncChunkLoadTimer->stopTiming();
2005 
2006  if(($chunk = $this->provider->getChunk($x, $z)) !== null){
2007  $this->chunks[$index] = $chunk;
2008  $chunk->initChunk();
2009  }else{
2010  return false;
2011  }
2012  }
2013 
2014  $this->server->getPluginManager()->callEvent(new ChunkLoadEvent($chunk, !$chunk->isGenerated()));
2015 
2016  return true;
2017  }
2018 
2019  protected function queueUnloadChunk($x, $z){
2020  $this->unloadQueue[Level::chunkHash($x, $z)] = microtime(true);
2021  }
2022 
2023  public function unloadChunkRequest($x, $z, $safe = true){
2024  if(($safe === true and $this->isChunkInUse($x, $z)) or $this->isSpawnChunk($x, $z)){
2025  return false;
2026  }
2027 
2028  $this->queueUnloadChunk($x, $z);
2029 
2030  return true;
2031  }
2032 
2033  public function cancelUnloadChunkRequest($x, $z){
2034  unset($this->unloadQueue[Level::chunkHash($x, $z)]);
2035  }
2036 
2037  public function unloadChunk($x, $z, $safe = true){
2038  if(($safe === true and $this->isChunkInUse($x, $z))){
2039  return false;
2040  }
2041 
2042  $this->timings->doChunkUnload->startTiming();
2043 
2044  $index = Level::chunkHash($x, $z);
2045 
2046  $chunk = $this->getChunk($x, $z);
2047 
2048  if($chunk !== null){
2049  $this->server->getPluginManager()->callEvent($ev = new ChunkUnloadEvent($chunk));
2050  if($ev->isCancelled()){
2051  return false;
2052  }
2053  }
2054 
2055  try{
2056  if($chunk !== null and $chunk->hasChanged() and $this->getAutoSave()){
2057  $this->provider->setChunk($x, $z, $chunk);
2058  $this->provider->saveChunk($x, $z);
2059  }
2060  $this->provider->unloadChunk($x, $z, $safe);
2061  }catch(\Exception $e){
2062  $logger = $this->server->getLogger();
2063  $logger->error("Error when unloading a chunk: " . $e->getMessage());
2064  if($logger instanceof MainLogger){
2065  $logger->logException($e);
2066  }
2067  }
2068 
2069  unset($this->chunks[$index]);
2070  unset($this->usedChunks[$index]);
2071  Cache::remove("world:" . $this->getId() . ":$index");
2072 
2073  $this->timings->doChunkUnload->stopTiming();
2074 
2075  return true;
2076  }
2077 
2086  public function isSpawnChunk($X, $Z){
2087  $spawnX = $this->provider->getSpawn()->getX() >> 4;
2088  $spawnZ = $this->provider->getSpawn()->getZ() >> 4;
2089 
2090  return abs($X - $spawnX) <= 1 and abs($Z - $spawnZ) <= 1;
2091  }
2092 
2099  public function getSpawn(){
2100  return $this->getSpawnLocation();
2101  }
2102 
2108  public function getSafeSpawn($spawn = null){
2109  if(!($spawn instanceof Vector3)){
2110  $spawn = $this->getSpawnLocation();
2111  }
2112  if($spawn instanceof Vector3){
2113  $v = $spawn->floor();
2114  $chunk = $this->getChunk($v->x >> 4, $v->z >> 4, false);
2115  $x = $v->x & 0x0f;
2116  $z = $v->z & 0x0f;
2117  if($chunk !== null){
2118  for(; $v->y > 0; --$v->y){
2119  if($v->y < 127 and Block::$solid[$chunk->getBlockId($x, $v->y & 0x7f, $z)]){
2120  $v->y++;
2121  break;
2122  }
2123  }
2124  for(; $v->y < 128; ++$v->y){
2125  if(!Block::$solid[$chunk->getBlockId($x, $v->y + 1, $z)]){
2126  if(!Block::$solid[$chunk->getBlockId($x, $v->y, $z)]){
2127  return new Position($spawn->x, $v->y === Math::floorFloat($spawn->y) ? $spawn->y : $v->y, $spawn->z, $this);
2128  }
2129  }else{
2130  ++$v->y;
2131  }
2132  }
2133  }
2134 
2135  return new Position($spawn->x, $v->y, $spawn->z, $this);
2136  }
2137 
2138  return false;
2139  }
2140 
2148  public function setSpawn(Vector3 $pos){
2149  $this->setSpawnLocation($pos);
2150  }
2151 
2157  public function getTime(){
2158  return (int) $this->time;
2159  }
2160 
2166  public function getName(){
2167  return $this->provider->getName();
2168  }
2169 
2175  public function getFolderName(){
2176  return $this->folderName;
2177  }
2178 
2184  public function setTime($time){
2185  $this->time = (int) $time;
2186  $this->sendTime();
2187  }
2188 
2192  public function stopTime(){
2193  $this->stopTime = true;
2194  $this->sendTime();
2195  }
2196 
2200  public function startTime(){
2201  $this->stopTime = false;
2202  $this->sendTime();
2203  }
2204 
2210  public function getSeed(){
2211  return $this->provider->getSeed();
2212  }
2213 
2219  public function setSeed($seed){
2220  $this->provider->setSeed($seed);
2221  }
2222 
2223 
2224  public function generateChunk($x, $z){
2225  if(!isset($this->chunkGenerationQueue[$index = Level::chunkHash($x, $z)])){
2226  $this->chunkGenerationQueue[$index] = true;
2227  $this->server->getGenerationManager()->requestChunk($this, $x, $z);
2228  }
2229  }
2230 
2231  public function regenerateChunk($x, $z){
2232  $this->unloadChunk($x, $z, false);
2233 
2234  $this->cancelUnloadChunkRequest($x, $z);
2235 
2236  $this->generateChunk($x, $z);
2237  //TODO: generate & refresh chunk from the generator object
2238  }
2239 
2240  public function doChunkGarbageCollection(){
2241  $this->timings->doChunkGC->startTiming();
2242 
2243  $X = null;
2244  $Z = null;
2245 
2246  foreach($this->chunks as $index => $chunk){
2247  if(!isset($this->unloadQueue[$index]) and (!isset($this->usedChunks[$index]) or count($this->usedChunks[$index]) === 0)){
2248  Level::getXZ($index, $X, $Z);
2249  if(!$this->isSpawnChunk($X, $Z)){
2250  $this->unloadChunkRequest($X, $Z, true);
2251  }
2252  }
2253  }
2254 
2255  foreach($this->provider->getLoadedChunks() as $chunk){
2256  if(!isset($this->chunks[Level::chunkHash($chunk->getX(), $chunk->getZ())])){
2257  $this->provider->unloadChunk($chunk->getX(), $chunk->getZ(), false);
2258  }
2259  }
2260 
2261  $this->timings->doChunkGC->stopTiming();
2262  }
2263 
2264  protected function unloadChunks(){
2265  if(count($this->unloadQueue) > 0){
2266  $X = null;
2267  $Z = null;
2268  foreach($this->unloadQueue as $index => $time){
2269  Level::getXZ($index, $X, $Z);
2270 
2271  //If the chunk can't be unloaded, it stays on the queue
2272  if($this->unloadChunk($X, $Z, true)){
2273  unset($this->unloadQueue[$index]);
2274  }
2275  }
2276  }
2277  }
2278 
2279  public function setMetadata($metadataKey, MetadataValue $metadataValue){
2280  $this->server->getLevelMetadata()->setMetadata($this, $metadataKey, $metadataValue);
2281  }
2282 
2283  public function getMetadata($metadataKey){
2284  return $this->server->getLevelMetadata()->getMetadata($this, $metadataKey);
2285  }
2286 
2287  public function hasMetadata($metadataKey){
2288  return $this->server->getLevelMetadata()->hasMetadata($this, $metadataKey);
2289  }
2290 
2291  public function removeMetadata($metadataKey, Plugin $plugin){
2292  $this->server->getLevelMetadata()->removeMetadata($this, $metadataKey, $plugin);
2293  }
2294 }
setBlockSkyLightAt($x, $y, $z, $level)
Definition: Level.php:1635
getMetadata($metadataKey)
Definition: Level.php:2283
setComponents($x, $y, $z)
Definition: Vector3.php:320
getEntity($entityId)
Definition: Level.php:1421
freeChunk($X, $Z, Player $player)
Definition: Level.php:447
loadChunk($x, $z, $generate=true)
Definition: Level.php:1990
getBlockLightAt($x, $y, $z)
Definition: Level.php:1648
getSafeSpawn($spawn=null)
Definition: Level.php:2108
setBlock(Vector3 $pos, Block $block, $direct=false, $update=true)
Definition: Level.php:1094
setBlockDataAt($x, $y, $z, $data)
Definition: Level.php:1609
getChunk($x, $z, $create=false)
Definition: Level.php:1732
useChunk($X, $Z, Player $player)
Definition: Level.php:433
add($x, $y=0, $z=0)
Definition: Vector3.php:94
getBlock(Vector3 $pos, $cached=true)
Definition: Level.php:960
getNearbyEntities(AxisAlignedBB $bb, Entity $entity=null)
Definition: Level.php:1473
static add($identifier, $blob, $minTTL=30)
Definition: Cache.php:35
static createEntity($type, FullChunk $chunk, Compound $nbt,...$args)
Definition: Entity.php:239
dropItem(Vector3 $source, Item $item, Vector3 $motion=null, $delay=10)
Definition: Level.php:1159
getUsingChunk($X, $Z)
Definition: Level.php:421
static remove($identifier)
Definition: Cache.php:68
useBreakOn(Vector3 $vector, Item &$item=null, Player $player=null)
Definition: Level.php:1201
addEntity(Entity $entity)
Definition: Level.php:1937
save($force=false)
Definition: Level.php:697
getTileById($tileId)
Definition: Level.php:1508
getFullBlock($x, $y, $z)
Definition: Level.php:948
getChunkAt($x, $z, $create=false)
Definition: Level.php:1751
static broadcastPacket(array $players, DataPacket $packet)
Definition: Server.php:1725
updateAround(Vector3 $pos)
Definition: Level.php:727
getBlockDataAt($x, $y, $z)
Definition: Level.php:1597
isFullBlock(Vector3 $pos)
Definition: Level.php:807
isChunkGenerated($x, $z)
Definition: Level.php:1812
unload($force=false)
Definition: Level.php:380
getHighestBlockAt($x, $z)
Definition: Level.php:1792
static get($identifier)
Definition: Cache.php:46
setMetadata($metadataKey, MetadataValue $metadataValue)
Definition: Level.php:2279
removeMetadata($metadataKey, Plugin $plugin)
Definition: Level.php:2291
hasMetadata($metadataKey)
Definition: Level.php:2287
setBlockIdAt($x, $y, $z, $id)
Definition: Level.php:1583
position(Position $v)
Definition: Block.php:985
scheduleUpdate(Vector3 $pos, $delay)
Definition: Level.php:763
setSpawnLocation(Vector3 $pos)
Definition: Level.php:1842
removeTile(Tile $tile)
Definition: Level.php:1964
getTile(Vector3 $pos)
Definition: Level.php:1528
setProvider(LevelProvider $provider)
setHeightMap($x, $z, $value)
Definition: Level.php:1719
useItemOn(Vector3 $vector, Item &$item, $face, $fx=0.0, $fy=0.0, $fz=0.0, Player $player=null)
Definition: Level.php:1297
addTile(Tile $tile)
Definition: Level.php:1952
getChunkEntities($X, $Z)
Definition: Level.php:1546
removeEntity(Entity $entity)
Definition: Level.php:1916
getCollisionBlocks(AxisAlignedBB $bb)
Definition: Level.php:776
getCollisionCubes(Entity $entity, AxisAlignedBB $bb, $entities=true)
Definition: Level.php:824
getCollidingEntities(AxisAlignedBB $bb, Entity $entity=null)
Definition: Level.php:1442
static chunkHash($x, $z)
Definition: Level.php:230
setBiomeId($x, $z, $biomeId)
Definition: Level.php:1699
getBlockIdAt($x, $y, $z)
Definition: Level.php:1571
setBiomeColor($x, $z, $R, $G, $B)
Definition: Level.php:1710
isChunkPopulated($x, $z)
Definition: Level.php:1823
setSpawn(Vector3 $pos)
Definition: Level.php:2148
getBlockSkyLightAt($x, $y, $z)
Definition: Level.php:1623
setBlockLightAt($x, $y, $z, $level)
Definition: Level.php:1660
static createTile($type, FullChunk $chunk, Compound $nbt,...$args)
Definition: Tile.php:74