PocketMine-MP  1.4 - API 1.10.0
 All Classes Namespaces Functions Variables Pages
Player.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 
22 namespace pocketmine;
23 
29 use pocketmine\entity\Item as DroppedItem;
93 use pocketmine\network\protocol\Info as ProtocolInfo;
116 
120 class Player extends Human implements CommandSender, InventoryHolder, IPlayer{
121 
122  const SURVIVAL = 0;
123  const CREATIVE = 1;
124  const ADVENTURE = 2;
125  const SPECTATOR = 3;
126  const VIEW = Player::SPECTATOR;
127 
128  const SURVIVAL_SLOTS = 36;
129  const CREATIVE_SLOTS = 112;
130 
132  protected $interface;
133 
134  public $spawned = false;
135  public $loggedIn = false;
136  public $gamemode;
137  public $lastBreak;
138 
139  protected $windowCnt = 2;
141  protected $windows;
143  protected $windowIndex = [];
144 
145  protected $sendIndex = 0;
146 
147  protected $moveToSend = [];
148  protected $motionToSend = [];
149 
150  public $blocked = false;
151  public $achievements = [];
152  public $lastCorrect;
154  protected $currentTransaction = null;
155  public $craftingType = 0; //0 = 2x2 crafting, 1 = 3x3 crafting, 2 = stonecutter
156 
157  protected $isCrafting = false;
158  public $loginData = [];
159  protected $lastMovement = 0;
161  protected $forceMovement = null;
162  protected $connected = true;
163  protected $ip;
164  protected $removeFormat = true;
165  protected $port;
166  protected $username;
167  protected $iusername;
168  protected $displayName;
169  protected $startAction = false;
171  protected $sleeping = null;
172  protected $clientID = null;
173 
174  protected $stepHeight = 0.6;
175 
176  public $usedChunks = [];
177  protected $loadQueue = [];
178  protected $chunkACK = [];
179  protected $nextChunkOrderRun = 5;
180 
182  protected $hiddenPlayers = [];
183 
185  protected $newPosition;
186 
187  protected $viewDistance;
188  protected $chunksPerTick;
190  private $spawnPosition = null;
191  private $inAction = false;
192 
193  protected $inAirTicks = 0;
194 
195 
196  private $needACK = [];
197 
201  protected $tasks = [];
202 
204  private $perm = null;
205 
206  public function isBanned(){
207  return $this->server->getNameBans()->isBanned(strtolower($this->getName()));
208  }
209 
210  public function setBanned($value){
211  if($value === true){
212  $this->server->getNameBans()->addBan($this->getName(), null, null, null);
213  }else{
214  $this->server->getNameBans()->remove($this->getName());
215  }
216  }
217 
218  public function isWhitelisted(){
219  return $this->server->isWhitelisted(strtolower($this->getName()));
220  }
221 
222  public function setWhitelisted($value){
223  if($value === true){
224  $this->server->addWhitelist(strtolower($this->getName()));
225  }else{
226  $this->server->removeWhitelist(strtolower($this->getName()));
227  }
228  }
229 
230  public function getPlayer(){
231  return $this;
232  }
233 
234  public function getFirstPlayed(){
235  return $this->namedtag instanceof Compound ? $this->namedtag["firstPlayed"] : null;
236  }
237 
238  public function getLastPlayed(){
239  return $this->namedtag instanceof Compound ? $this->namedtag["lastPlayed"] : null;
240  }
241 
242  public function hasPlayedBefore(){
243  return $this->namedtag instanceof Compound;
244  }
245 
246  protected function initEntity(){
247  parent::initEntity();
248  }
249 
253  public function spawnTo(Player $player){
254  if($this->spawned === true and $this->dead !== true and $player->dead !== true and $player->getLevel() === $this->level and $player->canSee($this)){
255  parent::spawnTo($player);
256  }
257  }
258 
262  public function getServer(){
263  return $this->server;
264  }
265 
269  public function getRemoveFormat(){
270  return $this->removeFormat;
271  }
272 
276  public function setRemoveFormat($remove = true){
277  $this->removeFormat = (bool) $remove;
278  }
279 
285  public function canSee(Player $player){
286  return !isset($this->hiddenPlayers[$player->getName()]);
287  }
288 
292  public function hidePlayer(Player $player){
293  if($player === $this){
294  return;
295  }
296  $this->hiddenPlayers[$player->getName()] = $player;
297  $player->despawnFrom($this);
298  }
299 
303  public function showPlayer(Player $player){
304  if($player === $this){
305  return;
306  }
307  unset($this->hiddenPlayers[$player->getName()]);
308  if($player->isOnline()){
309  $player->spawnTo($this);
310  }
311  }
312 
313  public function canCollideWith(Entity $entity){
314  return false;
315  }
316 
320  public function isOnline(){
321  return $this->connected === true and $this->loggedIn === true;
322  }
323 
327  public function isOp(){
328  return $this->server->isOp($this->getName());
329  }
330 
334  public function setOp($value){
335  if($value === $this->isOp()){
336  return;
337  }
338 
339  if($value === true){
340  $this->server->addOp($this->getName());
341  }else{
342  $this->server->removeOp($this->getName());
343  }
344 
345  $this->recalculatePermissions();
346  }
347 
353  public function isPermissionSet($name){
354  return $this->perm->isPermissionSet($name);
355  }
356 
362  public function hasPermission($name){
363  return $this->perm->hasPermission($name);
364  }
365 
373  public function addAttachment(Plugin $plugin, $name = null, $value = null){
374  return $this->perm->addAttachment($plugin, $name, $value);
375  }
376 
380  public function removeAttachment(PermissionAttachment $attachment){
381  $this->perm->removeAttachment($attachment);
382  }
383 
384  public function recalculatePermissions(){
385  $this->server->getPluginManager()->unsubscribeFromPermission(Server::BROADCAST_CHANNEL_USERS, $this);
386  $this->server->getPluginManager()->unsubscribeFromPermission(Server::BROADCAST_CHANNEL_ADMINISTRATIVE, $this);
387 
388  $this->perm->recalculatePermissions();
389 
390  if($this->hasPermission(Server::BROADCAST_CHANNEL_USERS)){
391  $this->server->getPluginManager()->subscribeToPermission(Server::BROADCAST_CHANNEL_USERS, $this);
392  }
393  if($this->hasPermission(Server::BROADCAST_CHANNEL_ADMINISTRATIVE)){
394  $this->server->getPluginManager()->subscribeToPermission(Server::BROADCAST_CHANNEL_ADMINISTRATIVE, $this);
395  }
396  }
397 
401  public function getEffectivePermissions(){
402  return $this->perm->getEffectivePermissions();
403  }
404 
405 
412  public function __construct(SourceInterface $interface, $clientID, $ip, $port){
413  $this->interface = $interface;
414  $this->windows = new \SplObjectStorage();
415  $this->perm = new PermissibleBase($this);
416  $this->namedtag = new Compound();
417  $this->server = Server::getInstance();
418  $this->lastBreak = microtime(true);
419  $this->ip = $ip;
420  $this->port = $port;
421  $this->clientID = $clientID;
422  $this->chunksPerTick = (int) $this->server->getProperty("chunk-sending.per-tick", 4);
423  $this->spawnPosition = null;
424  $this->gamemode = $this->server->getGamemode();
425  $this->setLevel($this->server->getDefaultLevel(), true);
426  $this->viewDistance = $this->server->getViewDistance();
427  $this->newPosition = new Vector3(0, 0, 0);
428  $this->boundingBox = new AxisAlignedBB(0, 0, 0, 0, 0, 0);
429  }
430 
434  public function removeAchievement($achievementId){
435  if($this->hasAchievement($achievementId)){
436  $this->achievements[$achievementId] = false;
437  }
438  }
439 
445  public function hasAchievement($achievementId){
446  if(!isset(Achievement::$list[$achievementId]) or !isset($this->achievements)){
447  $this->achievements = [];
448 
449  return false;
450  }
451 
452  if(!isset($this->achievements[$achievementId]) or $this->achievements[$achievementId] == false){
453  return false;
454  }
455 
456  return true;
457  }
458 
462  public function isConnected(){
463  return $this->connected === true;
464  }
465 
471  public function getDisplayName(){
472  return $this->displayName;
473  }
474 
478  public function setDisplayName($name){
479  $this->displayName = $name;
480  }
481 
485  public function getNameTag(){
486  return $this->nameTag;
487  }
488 
492  public function setNameTag($name){
493  $this->nameTag = $name;
494  $this->despawnFromAll();
495  if($this->spawned === true){
496  $this->spawnToAll();
497  }
498  }
499 
505  public function getAddress(){
506  return $this->ip;
507  }
508 
512  public function getPort(){
513  return $this->port;
514  }
515 
519  public function isSleeping(){
520  return $this->sleeping !== null;
521  }
522 
523  public function unloadChunk($x, $z){
524  $index = Level::chunkHash($x, $z);
525  if(isset($this->usedChunks[$index])){
526  foreach($this->level->getChunkEntities($x, $z) as $entity){
527  if($entity !== $this){
528  $entity->despawnFrom($this);
529  }
530  }
531 
532  unset($this->usedChunks[$index]);
533  }
534  $this->level->freeChunk($x, $z, $this);
535  unset($this->loadQueue[$index]);
536  }
537 
541  public function getSpawn(){
542  if($this->spawnPosition instanceof Position and $this->spawnPosition->getLevel() instanceof Level){
543  return $this->spawnPosition;
544  }else{
545  $level = $this->server->getDefaultLevel();
546 
547  return $level->getSafeSpawn();
548  }
549  }
550 
556  public function checkACK($identifier){
557  return !isset($this->needACK[$identifier]);
558  }
559 
560  public function handleACK($identifier){
561  unset($this->needACK[$identifier]);
562  if(isset($this->chunkACK[$identifier])){
563  $index = $this->chunkACK[$identifier];
564  unset($this->chunkACK[$identifier]);
565  if(isset($this->usedChunks[$index])){
566  $this->usedChunks[$index] = true;
567  $X = null;
568  $Z = null;
569  Level::getXZ($index, $X, $Z);
570 
571  foreach($this->level->getChunkEntities($X, $Z) as $entity){
572  if($entity !== $this and !$entity->closed and !$entity->dead){
573  $entity->spawnTo($this);
574  }
575  }
576  }
577  }
578  }
579 
580  public function sendChunk($x, $z, $payload){
581  if($this->connected === false){
582  return;
583  }
584 
585  $pk = new FullChunkDataPacket();
586  $pk->chunkX = $x;
587  $pk->chunkZ = $z;
588  $pk->data = $payload;
589  $cnt = $this->dataPacket($pk, true);
590  if($cnt === false or $cnt === true){
591  return;
592  }
593  $this->chunkACK[$cnt] = Level::chunkHash($x, $z);
594  }
595 
596  protected function sendNextChunk(){
597  if($this->connected === false){
598  return;
599  }
600 
601  $count = 0;
602  foreach($this->loadQueue as $index => $distance){
603  if($count >= $this->chunksPerTick){
604  break;
605  }
606 
607  $X = null;
608  $Z = null;
609  Level::getXZ($index, $X, $Z);
610 
611  if(!$this->level->isChunkPopulated($X, $Z)){
612  $this->level->generateChunk($X, $Z);
613  if($this->spawned){
614  continue;
615  }else{
616  break;
617  }
618  }
619 
620  ++$count;
621 
622  unset($this->loadQueue[$index]);
623  $this->usedChunks[$index] = false;
624 
625  $this->level->useChunk($X, $Z, $this);
626  $this->level->requestChunk($X, $Z, $this, LevelProvider::ORDER_ZXY);
627  }
628 
629  if(count($this->usedChunks) >= 56 and $this->spawned === false){
630  $spawned = 0;
631  foreach($this->usedChunks as $d){
632  if($d === true){
633  $spawned++;
634  }
635  }
636 
637  if($spawned < 56){
638  return;
639  }
640 
641  $this->spawned = true;
642 
643  $pk = new SetTimePacket();
644  $pk->time = $this->level->getTime();
645  $pk->started = $this->level->stopTime == false;
646  $this->dataPacket($pk);
647 
648  $pos = $this->level->getSafeSpawn($this);
649 
650  $this->server->getPluginManager()->callEvent($ev = new PlayerRespawnEvent($this, $pos));
651 
652  $this->teleport($ev->getRespawnPosition());
653 
654  $this->sendSettings();
655  $this->inventory->sendContents($this);
656  $this->inventory->sendArmorContents($this);
657 
658  $this->server->getPluginManager()->callEvent($ev = new PlayerJoinEvent($this, TextFormat::YELLOW . $this->getName() . " joined the game"));
659  if(strlen(trim($ev->getJoinMessage())) > 0){
660  $this->server->broadcastMessage($ev->getJoinMessage());
661  }
662 
663  $this->noDamageTicks = 60;
664 
665  $this->spawnToAll();
666 
667  if($this->server->getUpdater()->hasUpdate() and $this->hasPermission(Server::BROADCAST_CHANNEL_ADMINISTRATIVE)){
668  $this->server->getUpdater()->showPlayerUpdate($this);
669  }
670  }
671  }
672 
673  protected function orderChunks(){
674  if($this->connected === false){
675  return false;
676  }
677 
678  $this->nextChunkOrderRun = 200;
679 
680  $radiusSquared = $this->viewDistance;
681  $radius = ceil(sqrt($radiusSquared));
682  $side = ceil($radius / 2);
683 
684  $newOrder = [];
685  $lastChunk = $this->usedChunks;
686  $currentQueue = [];
687  $centerX = $this->x >> 4;
688  $centerZ = $this->z >> 4;
689  for($X = -$side; $X <= $side; ++$X){
690  for($Z = -$side; $Z <= $side; ++$Z){
691  $chunkX = $X + $centerX;
692  $chunkZ = $Z + $centerZ;
693  if(!isset($this->usedChunks[$index = Level::chunkHash($chunkX, $chunkZ)])){
694  $newOrder[$index] = abs($X) + abs($Z);
695  }else{
696  $currentQueue[$index] = abs($X) + abs($Z);
697  }
698  }
699  }
700  asort($newOrder);
701  asort($currentQueue);
702 
703 
704  $limit = $this->viewDistance;
705  foreach($currentQueue as $index => $distance){
706  if($limit-- <= 0){
707  break;
708  }
709  unset($lastChunk[$index]);
710  }
711 
712  foreach($lastChunk as $index => $Yndex){
713  $X = null;
714  $Z = null;
715  Level::getXZ($index, $X, $Z);
716  $this->unloadChunk($X, $Z);
717  }
718 
719  $loadedChunks = count($this->usedChunks);
720 
721  if((count($newOrder) + $loadedChunks) > $this->viewDistance){
722  $count = $loadedChunks;
723  $this->loadQueue = [];
724  foreach($newOrder as $k => $distance){
725  if(++$count > $this->viewDistance){
726  break;
727  }
728  $this->loadQueue[$k] = $distance;
729  }
730  }else{
731  $this->loadQueue = $newOrder;
732  }
733 
734  return true;
735  }
736 
745  public function dataPacket(DataPacket $packet, $needACK = false){
746  if($this->connected === false){
747  return false;
748  }
749  $this->server->getPluginManager()->callEvent($ev = new DataPacketSendEvent($this, $packet));
750  if($ev->isCancelled()){
751  return false;
752  }
753 
754  $identifier = $this->interface->putPacket($this, $packet, $needACK, false);
755 
756  if($needACK and $identifier !== null){
757  $this->needACK[$identifier] = false;
758 
759  return $identifier;
760  }
761 
762  return true;
763  }
764 
771  public function directDataPacket(DataPacket $packet, $needACK = false){
772  if($this->connected === false){
773  return false;
774  }
775  $this->server->getPluginManager()->callEvent($ev = new DataPacketSendEvent($this, $packet));
776  if($ev->isCancelled()){
777  return false;
778  }
779 
780  $identifier = $this->interface->putPacket($this, $packet, $needACK, true);
781 
782  if($needACK and $identifier !== null){
783  $this->needACK[$identifier] = false;
784 
785  return $identifier;
786  }
787 
788  return true;
789  }
790 
796  public function sleepOn(Vector3 $pos){
797  foreach($this->level->getNearbyEntities($this->boundingBox->grow(2, 1, 2), $this) as $p){
798  if($p instanceof Player){
799  if($p->sleeping !== null){
800  if($pos->distance($p->sleeping) <= 0.1){
801  return false;
802  }
803  }
804  }
805  }
806 
807  $this->server->getPluginManager()->callEvent($ev = new PlayerBedEnterEvent($this, $this->level->getBlock($pos)));
808  if($ev->isCancelled()){
809  return false;
810  }
811 
812  $this->sleeping = clone $pos;
813  $this->teleport(new Position($pos->x + 0.5, $pos->y + 1, $pos->z + 0.5, $this->level));
814 
815  $this->sendMetadata($this->getViewers());
816  $this->sendMetadata($this);
817 
818  $this->setSpawn($pos);
819  $this->tasks[] = $this->server->getScheduler()->scheduleDelayedTask(new CallbackTask([$this, "checkSleep"]), 60);
820 
821 
822  return true;
823  }
824 
830  public function setSpawn(Vector3 $pos){
831  if(!($pos instanceof Position)){
832  $level = $this->level;
833  }else{
834  $level = $pos->getLevel();
835  }
836  $this->spawnPosition = new Position($pos->x, $pos->y, $pos->z, $level);
837  $pk = new SetSpawnPositionPacket();
838  $pk->x = (int) $this->spawnPosition->x;
839  $pk->y = (int) $this->spawnPosition->y;
840  $pk->z = (int) $this->spawnPosition->z;
841  $this->dataPacket($pk);
842  }
843 
844  public function stopSleep(){
845  if($this->sleeping instanceof Vector3){
846  $this->server->getPluginManager()->callEvent($ev = new PlayerBedLeaveEvent($this, $this->level->getBlock($this->sleeping)));
847 
848  $this->sleeping = null;
849 
850  $this->sendMetadata($this->getViewers());
851  $this->sendMetadata($this);
852  }
853 
854  }
855 
860  public function checkSleep(){
861  if($this->sleeping instanceof Vector3){
862  //TODO: Move to Level
863 
864  $time = $this->level->getTime() % Level::TIME_FULL;
865 
866  if($time >= Level::TIME_NIGHT and $time < Level::TIME_SUNRISE){
867  foreach($this->level->getPlayers() as $p){
868  if($p->sleeping === null){
869  return;
870  }
871  }
872 
873  $this->level->setTime($this->level->getTime() + Level::TIME_FULL - $time);
874 
875  foreach($this->level->getPlayers() as $p){
876  $p->stopSleep();
877  }
878  }
879  }
880 
881  return;
882  }
883 
889  public function awardAchievement($achievementId){
890  if(isset(Achievement::$list[$achievementId]) and !$this->hasAchievement($achievementId)){
891  foreach(Achievement::$list[$achievementId]["requires"] as $requerimentId){
892  if(!$this->hasAchievement($requerimentId)){
893  return false;
894  }
895  }
896  $this->server->getPluginManager()->callEvent($ev = new PlayerAchievementAwardedEvent($this, $achievementId));
897  if(!$ev->isCancelled()){
898  $this->achievements[$achievementId] = true;
899  Achievement::broadcast($this, $achievementId);
900 
901  return true;
902  }else{
903  return false;
904  }
905  }
906 
907  return false;
908  }
909 
913  public function getGamemode(){
914  return $this->gamemode;
915  }
916 
925  public function setGamemode($gm){
926  if($gm < 0 or $gm > 3 or $this->gamemode === $gm){
927  return false;
928  }
929 
930  $this->server->getPluginManager()->callEvent($ev = new PlayerGameModeChangeEvent($this, (int) $gm));
931  if($ev->isCancelled()){
932  return false;
933  }
934 
935  if(($this->gamemode & 0x01) === ($gm & 0x01)){
936  $this->gamemode = $gm;
937  $this->sendMessage("Your gamemode has been changed to " . Server::getGamemodeString($this->getGamemode()) . ".\n");
938  }else{
939  $this->gamemode = $gm;
940  $this->sendMessage("Your gamemode has been changed to " . Server::getGamemodeString($this->getGamemode()) . ".\n");
941  $this->inventory->clearAll();
942  $this->inventory->sendContents($this->getViewers());
943  $this->inventory->sendHeldItem($this->hasSpawned);
944  }
945 
946  $this->namedtag->playerGameType = new Int("playerGameType", $this->gamemode);
947 
948  $spawnPosition = $this->getSpawn();
949 
950  $pk = new StartGamePacket();
951  $pk->seed = $this->level->getSeed();
952  $pk->x = $this->x;
953  $pk->y = $this->y + $this->getEyeHeight();
954  $pk->z = $this->z;
955  $pk->spawnX = (int) $spawnPosition->x;
956  $pk->spawnY = (int) $spawnPosition->y;
957  $pk->spawnZ = (int) $spawnPosition->z;
958  $pk->generator = 1; //0 old, 1 infinite, 2 flat
959  $pk->gamemode = $this->gamemode & 0x01;
960  $pk->eid = 0; //Always use EntityID as zero for the actual player
961  $this->dataPacket($pk);
962  $this->sendSettings();
963 
964  return true;
965  }
966 
975  public function sendSettings($nametags = true){
976  /*
977  bit mask | flag name
978  0x00000001 world_inmutable
979  0x00000002 -
980  0x00000004 -
981  0x00000008 - (autojump)
982  0x00000010 -
983  0x00000020 nametags_visible
984  0x00000040 ?
985  0x00000080 ?
986  0x00000100 ?
987  0x00000200 ?
988  0x00000400 ?
989  0x00000800 ?
990  0x00001000 ?
991  0x00002000 ?
992  0x00004000 ?
993  0x00008000 ?
994  0x00010000 ?
995  0x00020000 ?
996  0x00040000 ?
997  0x00080000 ?
998  0x00100000 ?
999  0x00200000 ?
1000  0x00400000 ?
1001  0x00800000 ?
1002  0x01000000 ?
1003  0x02000000 ?
1004  0x04000000 ?
1005  0x08000000 ?
1006  0x10000000 ?
1007  0x20000000 ?
1008  0x40000000 ?
1009  0x80000000 ?
1010  */
1011  $flags = 0;
1012  if($this->isAdventure()){
1013  $flags |= 0x01; //Do not allow placing/breaking blocks, adventure mode
1014  }
1015 
1016  if($nametags !== false){
1017  $flags |= 0x20; //Show Nametags
1018  }
1019 
1020  $pk = new AdventureSettingsPacket();
1021  $pk->flags = $flags;
1022  $this->dataPacket($pk);
1023  }
1024 
1025  public function isSurvival(){
1026  return ($this->gamemode & 0x01) === 0;
1027  }
1028 
1029  public function isCreative(){
1030  return ($this->gamemode & 0x01) > 0;
1031  }
1032 
1033  public function isAdventure(){
1034  return ($this->gamemode & 0x02) > 0;
1035  }
1036 
1037  public function getDrops(){
1038  if(!$this->isCreative()){
1039  return parent::getDrops();
1040  }
1041 
1042  return [];
1043  }
1044 
1045  protected function getCreativeBlock(Item $item){
1046  foreach(Block::$creative as $i => $d){
1047  if($d[0] === $item->getId() and $d[1] === $item->getDamage()){
1048  return $i;
1049  }
1050  }
1051 
1052  return -1;
1053  }
1054 
1055  public function addEntityMotion($entityId, $x, $y, $z){
1056  $this->motionToSend[$entityId] = [$entityId, $x, $y, $z];
1057  }
1058 
1059  public function addEntityMovement($entityId, $x, $y, $z, $yaw, $pitch){
1060  $this->moveToSend[$entityId] = [$entityId, $x, $y, $z, $yaw, $pitch];
1061  }
1062 
1063  protected function processMovement($currentTick){
1064  if($this->dead or !$this->spawned or !($this->newPosition instanceof Vector3)){
1065  return;
1066  }
1067 
1068  $distanceSquared = $this->newPosition->distanceSquared($this);
1069 
1070  $revert = false;
1071 
1072  if($distanceSquared > 10000){
1073  $revert = true;
1074  }else{
1075  if($this->chunk === null or !$this->chunk->isGenerated()){
1076  $chunk = $this->level->getChunk($this->newPosition->x >> 4, $this->newPosition->z >> 4);
1077  if(!($chunk instanceof FullChunk) or !$chunk->isGenerated()){
1078  $revert = true;
1079  $this->nextChunkOrderRun = 0;
1080  }else{
1081  if($this->chunk instanceof FullChunk){
1082  $this->chunk->removeEntity($this);
1083  }
1084  $this->chunk = $chunk;
1085  }
1086  }
1087  }
1088 
1089  if(!$revert and $distanceSquared != 0){
1090  $dx = $this->newPosition->x - $this->x;
1091  $dy = $this->newPosition->y - $this->y;
1092  $dz = $this->newPosition->z - $this->z;
1093 
1094  //$this->inBlock = $this->checkObstruction($this->x, ($this->boundingBox->minY + $this->boundingBox->maxY) / 2, $this->z);
1095  $this->move($dx, $dy, $dz);
1096 
1097  $diffX = $this->x - $this->newPosition->x;
1098  $diffZ = $this->z - $this->newPosition->z;
1099  $diffY = $this->y - $this->newPosition->y;
1100  if($diffY > -0.5 or $diffY < 0.5){
1101  $diffY = 0;
1102  }
1103 
1104  $diff = $diffX ** 2 + $diffY ** 2 + $diffZ ** 2;
1105 
1106  if($this->isSurvival()){
1107  if(!$revert and !$this->isSleeping()){
1108  if($diff > 0.0625){
1109  $revert = true;
1110  $this->server->getLogger()->warning($this->getName()." moved wrongly!");
1111  }elseif($diff > 0){
1112  $this->x = $this->newPosition->x;
1113  $this->y = $this->newPosition->y;
1114  $this->z = $this->newPosition->z;
1115  $radius = $this->width / 2;
1116  $this->boundingBox->setBounds($this->x - $radius, $this->y + $this->ySize, $this->z - $radius, $this->x + $radius, $this->y + $this->height + $this->ySize, $this->z + $radius);
1117  }
1118  }
1119  }elseif($diff > 0){
1120  $this->x = $this->newPosition->x;
1121  $this->y = $this->newPosition->y;
1122  $this->z = $this->newPosition->z;
1123  $radius = $this->width / 2;
1124  $this->boundingBox->setBounds($this->x - $radius, $this->y + $this->ySize, $this->z - $radius, $this->x + $radius, $this->y + $this->height + $this->ySize, $this->z + $radius);
1125  }
1126  }
1127 
1128  $from = new Location($this->lastX, $this->lastY, $this->lastZ, $this->lastYaw, $this->lastPitch, $this->level);
1129  $to = $this->getLocation();
1130 
1131  $delta = pow($this->lastX - $to->x, 2) + pow($this->lastY - $to->y, 2) + pow($this->lastZ - $to->z, 2);
1132  $deltaAngle = abs($this->lastYaw - $to->yaw) + abs($this->lastPitch - $to->pitch);
1133 
1134  if(!$revert and ($delta > (1 / 16) or $deltaAngle > 10)){
1135 
1136  $isFirst = ($this->lastX === null or $this->lastY === null or $this->lastZ === null);
1137 
1138  $this->lastX = $to->x;
1139  $this->lastY = $to->y;
1140  $this->lastZ = $to->z;
1141 
1142  $this->lastYaw = $to->yaw;
1143  $this->lastPitch = $to->pitch;
1144 
1145  if(!$isFirst){
1146  $ev = new PlayerMoveEvent($this, $from, $to);
1147 
1148  $this->server->getPluginManager()->callEvent($ev);
1149 
1150  if(!($revert = $ev->isCancelled())){ //Yes, this is intended
1151  if($to->distanceSquared($ev->getTo()) > 0.01){ //If plugins modify the destination
1152  $this->teleport($ev->getTo());
1153  }else{
1154  $pk = new MovePlayerPacket();
1155  $pk->eid = $this->id;
1156  $pk->x = $this->x;
1157  $pk->y = $this->y;
1158  $pk->z = $this->z;
1159  $pk->yaw = $this->yaw;
1160  $pk->pitch = $this->pitch;
1161  $pk->bodyYaw = $this->yaw;
1162 
1163  Server::broadcastPacket($this->hasSpawned, $pk);
1164  }
1165  }
1166  }
1167  }
1168 
1169  if($revert){
1170 
1171  $this->lastX = $from->x;
1172  $this->lastY = $from->y;
1173  $this->lastZ = $from->z;
1174 
1175  $this->lastYaw = $from->yaw;
1176  $this->lastPitch = $from->pitch;
1177 
1178  $pk = new MovePlayerPacket();
1179  $pk->eid = 0;
1180  $pk->x = $from->x;
1181  $pk->y = $from->y + $this->getEyeHeight();
1182  $pk->z = $from->z;
1183  $pk->bodyYaw = $from->yaw;
1184  $pk->pitch = $from->pitch;
1185  $pk->yaw = $from->yaw;
1186  $pk->teleport = true;
1187  $this->directDataPacket($pk);
1188  $this->forceMovement = new Vector3($from->x, $from->y, $from->z);
1189  $this->newPosition = null;
1190  }else{
1191  $this->forceMovement = null;
1192  if($distanceSquared != 0 and $this->nextChunkOrderRun > 20){
1193  $this->nextChunkOrderRun = 20;
1194  }
1195  }
1196  }
1197 
1198  public function updateMovement(){
1199 
1200  }
1201 
1202  public function onUpdate($currentTick){
1203  if($this->dead === true and $this->spawned){
1204  ++$this->deadTicks;
1205  if($this->deadTicks >= 10){
1206  $this->despawnFromAll();
1207  }
1208  return $this->deadTicks < 10;
1209  }
1210 
1211  $this->timings->startTiming();
1212 
1213  $this->lastUpdate = $currentTick;
1214 
1215  if($this->spawned){
1216  $this->processMovement($currentTick);
1217 
1218  $this->entityBaseTick(1);
1219 
1220  if($this->onGround){
1221  $this->inAirTicks = 0;
1222  }else{
1223  if($this->inAirTicks > 100 and $this->isSurvival() and !$this->isSleeping() and $this->spawned and !$this->server->getAllowFlight()){
1224  $this->kick("Flying is not enabled on this server");
1225  return false;
1226  }else{
1227  ++$this->inAirTicks;
1228  }
1229  }
1230 
1231  foreach($this->level->getNearbyEntities($this->boundingBox->grow(1, 0.5, 1), $this) as $entity){
1232  if(($currentTick - $entity->lastUpdate) > 1){
1233  $entity->scheduleUpdate();
1234  }
1235 
1236  if($entity instanceof Arrow and $entity->onGround){
1237  if($entity->dead !== true){
1238  $item = Item::get(Item::ARROW, 0, 1);
1239  if($this->isSurvival() and !$this->inventory->canAddItem($item)){
1240  continue;
1241  }
1242 
1243  $this->server->getPluginManager()->callEvent($ev = new InventoryPickupArrowEvent($this->inventory, $entity));
1244  if($ev->isCancelled()){
1245  continue;
1246  }
1247 
1248  $pk = new TakeItemEntityPacket();
1249  $pk->eid = 0;
1250  $pk->target = $entity->getId();
1251  $this->dataPacket($pk);
1252  $pk = new TakeItemEntityPacket();
1253  $pk->eid = $this->getId();
1254  $pk->target = $entity->getId();
1255  Server::broadcastPacket($entity->getViewers(), $pk);
1256  $this->inventory->addItem(clone $item, $this);
1257  $entity->kill();
1258  }
1259  }elseif($entity instanceof DroppedItem){
1260  if($entity->dead !== true and $entity->getPickupDelay() <= 0){
1261  $item = $entity->getItem();
1262 
1263  if($item instanceof Item){
1264  if($this->isSurvival() and !$this->inventory->canAddItem($item)){
1265  continue;
1266  }
1267 
1268  $this->server->getPluginManager()->callEvent($ev = new InventoryPickupItemEvent($this->inventory, $entity));
1269  if($ev->isCancelled()){
1270  continue;
1271  }
1272 
1273  switch($item->getId()){
1274  case Item::WOOD:
1275  $this->awardAchievement("mineWood");
1276  break;
1277  case Item::DIAMOND:
1278  $this->awardAchievement("diamond");
1279  break;
1280  }
1281 
1282  $pk = new TakeItemEntityPacket();
1283  $pk->eid = 0;
1284  $pk->target = $entity->getId();
1285  $this->dataPacket($pk);
1286  $pk = new TakeItemEntityPacket();
1287  $pk->eid = $this->getId();
1288  $pk->target = $entity->getId();
1289  Server::broadcastPacket($entity->getViewers(), $pk);
1290  $this->inventory->addItem(clone $item, $this);
1291  $entity->kill();
1292  }
1293  }
1294  }
1295  }
1296  }
1297 
1298  if($this->nextChunkOrderRun-- <= 0 or $this->chunk === null){
1299  $this->orderChunks();
1300  }
1301 
1302  if(count($this->loadQueue) > 0 or !$this->spawned){
1303  $this->sendNextChunk();
1304  }
1305 
1306  if(count($this->moveToSend) > 0){
1307  $pk = new MoveEntityPacket();
1308  $pk->entities = $this->moveToSend;
1309  $this->dataPacket($pk);
1310  $this->moveToSend = [];
1311  }
1312 
1313 
1314  if(count($this->motionToSend) > 0){
1315  $pk = new SetEntityMotionPacket();
1316  $pk->entities = $this->motionToSend;
1317  $this->dataPacket($pk);
1318  $this->motionToSend = [];
1319  }
1320 
1321  $this->timings->stopTiming();
1322 
1323  return true;
1324  }
1325 
1335  public function handleDataPacket(DataPacket $packet){
1336  if($this->connected === false){
1337  return;
1338  }
1339 
1340  $this->server->getPluginManager()->callEvent($ev = new DataPacketReceiveEvent($this, $packet));
1341  if($ev->isCancelled()){
1342  return;
1343  }
1344 
1345  switch($packet->pid()){
1346  case ProtocolInfo::LOGIN_PACKET:
1347  if($this->loggedIn === true){
1348  break;
1349  }
1350 
1351  $this->username = TextFormat::clean($packet->username);
1352  $this->displayName = $this->username;
1353  $this->nameTag = $this->username;
1354  $this->iusername = strtolower($this->username);
1355  $this->loginData = ["clientId" => $packet->clientId, "loginData" => $packet->loginData];
1356 
1357  if(count($this->server->getOnlinePlayers()) > $this->server->getMaxPlayers()){
1358  if($this->kick("server full") === true){
1359  return;
1360  }
1361  }
1362  if($packet->protocol1 !== ProtocolInfo::CURRENT_PROTOCOL){
1363  if($packet->protocol1 < ProtocolInfo::CURRENT_PROTOCOL){
1364  $pk = new LoginStatusPacket();
1365  $pk->status = 1;
1366  $this->dataPacket($pk);
1367  }else{
1368  $pk = new LoginStatusPacket();
1369  $pk->status = 2;
1370  $this->dataPacket($pk);
1371  }
1372  $this->close("", "Incorrect protocol #" . $packet->protocol1, false);
1373 
1374  return;
1375  }
1376  if(strpos($packet->username, "\x00") !== false or preg_match('#^[a-zA-Z0-9_]{3,16}$#', $packet->username) == 0 or $this->username === "" or $this->iusername === "rcon" or $this->iusername === "console" or strlen($packet->username) > 16 or strlen($packet->username) < 3){
1377  $this->close("", "Bad username");
1378 
1379  return;
1380  }
1381 
1382  $this->server->getPluginManager()->callEvent($ev = new PlayerPreLoginEvent($this, "Plugin reason"));
1383  if($ev->isCancelled()){
1384  $this->close("", $ev->getKickMessage());
1385 
1386  return;
1387  }
1388 
1389  if(!$this->server->isWhitelisted(strtolower($this->getName()))){
1390  $this->close(TextFormat::YELLOW . $this->username . " has left the game", "Server is white-listed");
1391 
1392  return;
1393  }elseif($this->server->getNameBans()->isBanned(strtolower($this->getName())) or $this->server->getIPBans()->isBanned($this->getAddress())){
1394  $this->close(TextFormat::YELLOW . $this->username . " has left the game", "You are banned");
1395 
1396  return;
1397  }
1398 
1399  if($this->hasPermission(Server::BROADCAST_CHANNEL_USERS)){
1400  $this->server->getPluginManager()->subscribeToPermission(Server::BROADCAST_CHANNEL_USERS, $this);
1401  }
1402  if($this->hasPermission(Server::BROADCAST_CHANNEL_ADMINISTRATIVE)){
1403  $this->server->getPluginManager()->subscribeToPermission(Server::BROADCAST_CHANNEL_ADMINISTRATIVE, $this);
1404  }
1405 
1406  foreach($this->server->getOnlinePlayers() as $p){
1407  if($p !== $this and strtolower($p->getName()) === strtolower($this->getName())){
1408  if($p->kick("logged in from another location") === false){
1409  $this->close(TextFormat::YELLOW . $this->getName() . " has left the game", "Logged in from another location");
1410 
1411  return;
1412  }else{
1413  break;
1414  }
1415  }
1416  }
1417 
1418  $nbt = $this->server->getOfflinePlayerData($this->username);
1419  if(!isset($nbt->NameTag)){
1420  $nbt->NameTag = new String("NameTag", $this->username);
1421  }else{
1422  $nbt["NameTag"] = $this->username;
1423  }
1424  $this->gamemode = $nbt["playerGameType"] & 0x03;
1425  if($this->server->getForceGamemode()){
1426  $this->gamemode = $this->server->getGamemode();
1427  $nbt->playerGameType = new Int("playerGameType", $this->gamemode);
1428  }
1429  if(($level = $this->server->getLevelByName($nbt["Level"])) === null){
1430  $this->setLevel($this->server->getDefaultLevel(), true);
1431  $nbt["Level"] = $this->level->getName();
1432  $nbt["Pos"][0] = $this->level->getSpawnLocation()->x;
1433  $nbt["Pos"][1] = $this->level->getSpawnLocation()->y;
1434  $nbt["Pos"][2] = $this->level->getSpawnLocation()->z;
1435  }else{
1436  $this->setLevel($level, true);
1437  }
1438 
1439  if(!($nbt instanceof Compound)){
1440  $this->close(TextFormat::YELLOW . $this->username . " has left the game", "Invalid data");
1441 
1442  return;
1443  }
1444 
1445  $this->achievements = [];
1446 
1448  foreach($nbt->Achievements as $achievement){
1449  $this->achievements[$achievement->getName()] = $achievement->getValue() > 0 ? true : false;
1450  }
1451 
1452  $nbt["lastPlayed"] = floor(microtime(true) * 1000);
1453  $this->server->saveOfflinePlayerData($this->username, $nbt);
1454  parent::__construct($this->level->getChunk($nbt["Pos"][0] >> 4, $nbt["Pos"][2] >> 4, true), $nbt);
1455  $this->loggedIn = true;
1456 
1457  $this->server->getPluginManager()->callEvent($ev = new PlayerLoginEvent($this, "Plugin reason"));
1458  if($ev->isCancelled()){
1459  $this->close(TextFormat::YELLOW . $this->username . " has left the game", $ev->getKickMessage());
1460 
1461  return;
1462  }
1463 
1464  if($this->isCreative()){
1465  $this->inventory->setHeldItemSlot(0);
1466  }else{
1467  $this->inventory->setHeldItemSlot(0);
1468  }
1469 
1470  $pk = new LoginStatusPacket();
1471  $pk->status = 0;
1472  $this->dataPacket($pk);
1473 
1474  if($this->spawnPosition === null and isset($this->namedtag->SpawnLevel) and ($level = $this->server->getLevelByName($this->namedtag["SpawnLevel"])) instanceof Level){
1475  $this->spawnPosition = new Position($this->namedtag["SpawnX"], $this->namedtag["SpawnY"], $this->namedtag["SpawnZ"], $level);
1476  }
1477 
1478  $spawnPosition = $this->getSpawn();
1479 
1480  $this->dead = false;
1481 
1482  $pk = new StartGamePacket();
1483  $pk->seed = $this->level->getSeed();
1484  $pk->x = $this->x;
1485  $pk->y = $this->y;
1486  $pk->z = $this->z;
1487  $pk->spawnX = (int) $spawnPosition->x;
1488  $pk->spawnY = (int) $spawnPosition->y;
1489  $pk->spawnZ = (int) $spawnPosition->z;
1490  $pk->generator = 1; //0 old, 1 infinite, 2 flat
1491  $pk->gamemode = $this->gamemode & 0x01;
1492  $pk->eid = 0; //Always use EntityID as zero for the actual player
1493  $this->dataPacket($pk);
1494 
1495  $pk = new SetTimePacket();
1496  $pk->time = $this->level->getTime();
1497  $pk->started = $this->level->stopTime == false;
1498  $this->dataPacket($pk);
1499 
1500  $pk = new SetSpawnPositionPacket();
1501  $pk->x = (int) $spawnPosition->x;
1502  $pk->y = (int) $spawnPosition->y;
1503  $pk->z = (int) $spawnPosition->z;
1504  $this->dataPacket($pk);
1505 
1506  $pk = new SetHealthPacket();
1507  $pk->health = $this->getHealth();
1508  $this->dataPacket($pk);
1509  if($this->getHealth() <= 0){
1510  $this->dead = true;
1511  }
1512 
1513  $pk = new SetDifficultyPacket();
1514  $pk->difficulty = $this->server->getDifficulty();
1515  $this->dataPacket($pk);
1516 
1517  $this->server->getLogger()->info(TextFormat::AQUA . $this->username . TextFormat::WHITE . "[/" . $this->ip . ":" . $this->port . "] logged in with entity id " . $this->id . " at (" . $this->level->getName() . ", " . round($this->x, 4) . ", " . round($this->y, 4) . ", " . round($this->z, 4) . ")");
1518 
1519 
1520  $this->orderChunks();
1521  $this->sendNextChunk();
1522  break;
1523  case ProtocolInfo::ROTATE_HEAD_PACKET:
1524  if($this->spawned === false or $this->dead === true){
1525  break;
1526  }
1527  $this->setRotation($packet->yaw, $this->pitch);
1528  break;
1529  case ProtocolInfo::MOVE_PLAYER_PACKET:
1530 
1531  $newPos = new Vector3($packet->x, $packet->y, $packet->z);
1532 
1533  $revert = false;
1534  if($this->dead === true or $this->spawned !== true){
1535  $revert = true;
1536  $this->forceMovement = new Vector3($this->x, $this->y, $this->z);
1537  }
1538 
1539  if($this->forceMovement instanceof Vector3 and ($revert or $newPos->distanceSquared($this->forceMovement) > 0.04)){
1540  $pk = new MovePlayerPacket();
1541  $pk->eid = 0;
1542  $pk->x = $this->forceMovement->x;
1543  $pk->y = $this->forceMovement->y + $this->getEyeHeight();
1544  $pk->z = $this->forceMovement->z;
1545  $pk->bodyYaw = $packet->bodyYaw;
1546  $pk->pitch = $packet->pitch;
1547  $pk->yaw = $packet->yaw;
1548  $pk->teleport = true;
1549  $this->directDataPacket($pk);
1550  }else{
1551  $packet->yaw %= 360;
1552  $packet->pitch %= 360;
1553 
1554  if($packet->yaw < 0){
1555  $packet->yaw += 360;
1556  }
1557 
1558  $this->setRotation($packet->yaw, $packet->pitch);
1559  $this->newPosition = $newPos;
1560  $this->forceMovement = null;
1561  }
1562 
1563  break;
1564  case ProtocolInfo::PLAYER_EQUIPMENT_PACKET:
1565  if($this->spawned === false or $this->dead === true){
1566  break;
1567  }
1568 
1569  if($packet->slot === 0x28 or $packet->slot === 0 or $packet->slot === 255){ //0 for 0.8.0 compatibility
1570  $packet->slot = -1; //Air
1571  }else{
1572  $packet->slot -= 9; //Get real block slot
1573  }
1574 
1575  if($this->isCreative()){ //Creative mode match
1576  $item = Item::get($packet->item, $packet->meta, 1);
1577  $slot = $this->getCreativeBlock($item);
1578  }else{
1579  $item = $this->inventory->getItem($packet->slot);
1580  $slot = $packet->slot;
1581  }
1582 
1583  if($packet->slot === -1){ //Air
1584  if($this->isCreative()){
1585  $found = false;
1586  for($i = 0; $i < $this->inventory->getHotbarSize(); ++$i){
1587  if($this->inventory->getHotbarSlotIndex($i) === -1){
1588  $this->inventory->setHeldItemIndex($i);
1589  $found = true;
1590  break;
1591  }
1592  }
1593 
1594  if(!$found){ //couldn't find a empty slot (error)
1595  $this->inventory->sendContents($this);
1596  break;
1597  }
1598  }else{
1599  $this->inventory->setHeldItemSlot($packet->slot); //set Air
1600  }
1601  }elseif(!isset($item) or $slot === -1 or $item->getId() !== $packet->item or $item->getDamage() !== $packet->meta){ // packet error or not implemented
1602  $this->inventory->sendContents($this);
1603  break;
1604  }elseif($this->isCreative()){
1605  $item = Item::get(
1606  Block::$creative[$slot][0],
1607  Block::$creative[$slot][1],
1608  1
1609  );
1610  $this->inventory->setHeldItemIndex($packet->slot);
1611  }else{
1612  $this->inventory->setHeldItemSlot($slot);
1613  }
1614 
1615  $this->inventory->sendHeldItem($this->hasSpawned);
1616 
1617  if($this->inAction === true){
1618  $this->inAction = false;
1619  $this->sendMetadata($this->getViewers());
1620  }
1621  break;
1622  case ProtocolInfo::USE_ITEM_PACKET:
1623  if($this->spawned === false or $this->dead === true or $this->blocked){
1624  break;
1625  }
1626 
1627  $blockVector = new Vector3($packet->x, $packet->y, $packet->z);
1628 
1629  $this->craftingType = 0;
1630 
1631  $packet->eid = $this->id;
1632 
1633  if($packet->face >= 0 and $packet->face <= 5){ //Use Block, place
1634  if($this->inAction === true){
1635  $this->inAction = false;
1636  $this->sendMetadata($this->getViewers());
1637  }
1638 
1639  if($blockVector->distance($this) > 10){
1640 
1641  }elseif($this->isCreative()){
1642  $item = $this->inventory->getItemInHand();
1643  if($this->level->useItemOn($blockVector, $item, $packet->face, $packet->fx, $packet->fy, $packet->fz, $this) === true){
1644  break;
1645  }
1646  }elseif($this->inventory->getItemInHand()->getId() !== $packet->item or (($damage = $this->inventory->getItemInHand()->getDamage()) !== $packet->meta and $damage !== null)){
1647  $this->inventory->sendHeldItem($this);
1648  }else{
1649  $item = $this->inventory->getItemInHand();
1650  $oldItem = clone $item;
1651  //TODO: Implement adventure mode checks
1652  if($this->level->useItemOn($blockVector, $item, $packet->face, $packet->fx, $packet->fy, $packet->fz, $this) === true){
1653  if(!$item->equals($oldItem, true) or $item->getCount() !== $oldItem->getCount()){
1654  $this->inventory->setItemInHand($item, $this);
1655  $this->inventory->sendHeldItem($this->hasSpawned);
1656  }
1657  break;
1658  }
1659  }
1660  $target = $this->level->getBlock($blockVector);
1661  $block = $target->getSide($packet->face);
1662 
1663  $pk = new UpdateBlockPacket();
1664  $pk->x = $target->x;
1665  $pk->y = $target->y;
1666  $pk->z = $target->z;
1667  $pk->block = $target->getId();
1668  $pk->meta = $target->getDamage();
1669  $this->dataPacket($pk);
1670 
1671  $pk = new UpdateBlockPacket();
1672  $pk->x = $block->x;
1673  $pk->y = $block->y;
1674  $pk->z = $block->z;
1675  $pk->block = $block->getId();
1676  $pk->meta = $block->getDamage();
1677  $this->dataPacket($pk);
1678  break;
1679  }elseif($packet->face === 0xff){
1680  if($this->isCreative()){
1681  $item = $this->inventory->getItemInHand();
1682  }elseif($this->inventory->getItemInHand()->getId() !== $packet->item or (($damage = $this->inventory->getItemInHand()->getDamage()) !== $packet->meta and $damage !== null)){
1683  $this->inventory->sendHeldItem($this);
1684  break;
1685  }else{
1686  $item = $this->inventory->getItemInHand();
1687  }
1688  $target = $this->level->getBlock($blockVector);
1689 
1690  $ev = new PlayerInteractEvent($this, $item, $target, $packet->face);
1691 
1692  $this->server->getPluginManager()->callEvent($ev);
1693 
1694  if($ev->isCancelled()){
1695  $this->inventory->sendHeldItem($this);
1696  break;
1697  }
1698 
1699  if($item->getId() === Item::SNOWBALL){
1700  $nbt = new Compound("", [
1701  "Pos" => new Enum("Pos", [
1702  new Double("", $this->x),
1703  new Double("", $this->y + $this->getEyeHeight()),
1704  new Double("", $this->z)
1705  ]),
1706  "Motion" => new Enum("Motion", [
1707  new Double("", -sin($this->yaw / 180 * M_PI) * cos($this->pitch / 180 * M_PI)),
1708  new Double("", -sin($this->pitch / 180 * M_PI)),
1709  new Double("", cos($this->yaw / 180 * M_PI) * cos($this->pitch / 180 * M_PI))
1710  ]),
1711  "Rotation" => new Enum("Rotation", [
1712  new Float("", $this->yaw),
1713  new Float("", $this->pitch)
1714  ]),
1715  ]);
1716 
1717  $f = 1.5;
1718  $snowball = Entity::createEntity("Snowball", $this->chunk, $nbt, $this);
1719  $snowball->setMotion($snowball->getMotion()->multiply($f));
1720  if($this->isSurvival()){
1721  $this->inventory->removeItem(Item::get(Item::SNOWBALL, 0, 1), $this);
1722  }
1723  if($snowball instanceof Projectile){
1724  $this->server->getPluginManager()->callEvent($projectileEv = new ProjectileLaunchEvent($snowball));
1725  if($projectileEv->isCancelled()){
1726  $snowball->kill();
1727  }else{
1728  $snowball->spawnToAll();
1729  }
1730  }else{
1731  $snowball->spawnToAll();
1732  }
1733  }
1734  $this->inAction = true;
1735  $this->startAction = microtime(true);
1736  $this->sendMetadata($this->getViewers());
1737  }
1738  break;
1739  case ProtocolInfo::PLAYER_ACTION_PACKET:
1740  if($this->spawned === false or $this->blocked === true or $this->dead === true){
1741  break;
1742  }
1743 
1744  $this->craftingType = 0;
1745  $packet->eid = $this->id;
1746 
1747  switch($packet->action){
1748  case 5: //Shot arrow
1749  if($this->inventory->getItemInHand()->getId() === Item::BOW){
1750  $bow = $this->inventory->getItemInHand();
1751  if($this->isSurvival()){
1752  if(!$this->inventory->contains(Item::get(Item::ARROW, 0, 1))){
1753  $this->inventory->sendContents($this);
1754  return;
1755  }
1756  }
1757 
1758 
1759  $nbt = new Compound("", [
1760  "Pos" => new Enum("Pos", [
1761  new Double("", $this->x),
1762  new Double("", $this->y + $this->getEyeHeight()),
1763  new Double("", $this->z)
1764  ]),
1765  "Motion" => new Enum("Motion", [
1766  new Double("", -sin($this->yaw / 180 * M_PI) * cos($this->pitch / 180 * M_PI)),
1767  new Double("", -sin($this->pitch / 180 * M_PI)),
1768  new Double("", cos($this->yaw / 180 * M_PI) * cos($this->pitch / 180 * M_PI))
1769  ]),
1770  "Rotation" => new Enum("Rotation", [
1771  new Float("", $this->yaw),
1772  new Float("", $this->pitch)
1773  ]),
1774  ]);
1775 
1776  $f = 1.5;
1777  $ev = new EntityShootBowEvent($this, $bow, Entity::createEntity("Arrow", $this->chunk, $nbt, $this), $f);
1778 
1779  $this->server->getPluginManager()->callEvent($ev);
1780 
1781  if($ev->isCancelled()){
1782  $ev->getProjectile()->kill();
1783  }else{
1784  $ev->getProjectile()->setMotion($ev->getProjectile()->getMotion()->multiply($ev->getForce()));
1785  if($this->isSurvival()){
1786  $this->inventory->removeItem(Item::get(Item::ARROW, 0, 1), $this);
1787  $bow->setDamage($bow->getDamage() + 1);
1788  $this->inventory->setItemInHand($bow, $this);
1789  if($bow->getDamage() >= 385){
1790  $this->inventory->setItemInHand(Item::get(Item::AIR, 0, 0), $this);
1791  }
1792  }
1793  if($ev->getProjectile() instanceof Projectile){
1794  $this->server->getPluginManager()->callEvent($projectileEv = new ProjectileLaunchEvent($ev->getProjectile()));
1795  if($projectileEv->isCancelled()){
1796  $ev->getProjectile()->kill();
1797  }else{
1798  $ev->getProjectile()->spawnToAll();
1799  }
1800  }else{
1801  $ev->getProjectile()->spawnToAll();
1802  }
1803  }
1804  }
1805 
1806  $this->startAction = false;
1807  $this->inAction = false;
1808  $this->sendMetadata($this->getViewers());
1809  break;
1810  case 6: //get out of the bed
1811  $this->stopSleep();
1812  break;
1813  }
1814  break;
1815  case ProtocolInfo::REMOVE_BLOCK_PACKET:
1816  if($this->spawned === false or $this->blocked === true or $this->dead === true){
1817  break;
1818  }
1819  $this->craftingType = 0;
1820 
1821  $vector = new Vector3($packet->x, $packet->y, $packet->z);
1822 
1823 
1824  if($this->isCreative()){
1825  $item = $this->inventory->getItemInHand();
1826  }else{
1827  $item = $this->inventory->getItemInHand();
1828  }
1829 
1830  $oldItem = clone $item;
1831 
1832  if($this->level->useBreakOn($vector, $item, $this) === true){
1833  if($this->isSurvival()){
1834  if(!$item->equals($oldItem, true) or $item->getCount() !== $oldItem->getCount()){
1835  $this->inventory->setItemInHand($item, $this);
1836  $this->inventory->sendHeldItem($this->hasSpawned);
1837  }
1838  }
1839  break;
1840  }
1841 
1842  $this->inventory->sendContents($this);
1843  $target = $this->level->getBlock($vector);
1844  $tile = $this->level->getTile($vector);
1845 
1846  $pk = new UpdateBlockPacket();
1847  $pk->x = $target->x;
1848  $pk->y = $target->y;
1849  $pk->z = $target->z;
1850  $pk->block = $target->getId();
1851  $pk->meta = $target->getDamage();
1852  $this->dataPacket($pk);
1853 
1854  if($tile instanceof Spawnable){
1855  $tile->spawnTo($this);
1856  }
1857  break;
1858 
1859  case ProtocolInfo::PLAYER_ARMOR_EQUIPMENT_PACKET:
1860  break;
1861 
1862  case ProtocolInfo::INTERACT_PACKET:
1863  if($this->spawned === false or $this->dead === true or $this->blocked){
1864  break;
1865  }
1866 
1867  $this->craftingType = 0;
1868 
1869  $target = $this->level->getEntity($packet->target);
1870 
1871  $cancelled = false;
1872 
1873  if(
1874  $target instanceof Player and
1875  $this->server->getConfigBoolean("pvp", true) === false
1876 
1877  ){
1878  $cancelled = true;
1879  }
1880 
1881  if($target instanceof Entity and $this->getGamemode() !== Player::VIEW and $this->dead !== true and $target->dead !== true){
1882  if($target instanceof DroppedItem or $target instanceof Arrow){
1883  $this->kick("Attempting to attack an invalid entity");
1884  $this->server->getLogger()->warning("Player " . $this->getName() . " tried to attack an invalid entity");
1885  return;
1886  }
1887 
1888  $item = $this->inventory->getItemInHand();
1889  $damageTable = [
1890  Item::WOODEN_SWORD => 4,
1891  Item::GOLD_SWORD => 4,
1892  Item::STONE_SWORD => 5,
1893  Item::IRON_SWORD => 6,
1894  Item::DIAMOND_SWORD => 7,
1895 
1896  Item::WOODEN_AXE => 3,
1897  Item::GOLD_AXE => 3,
1898  Item::STONE_AXE => 3,
1899  Item::IRON_AXE => 5,
1900  Item::DIAMOND_AXE => 6,
1901 
1902  Item::WOODEN_PICKAXE => 2,
1903  Item::GOLD_PICKAXE => 2,
1904  Item::STONE_PICKAXE => 3,
1905  Item::IRON_PICKAXE => 4,
1906  Item::DIAMOND_PICKAXE => 5,
1907 
1908  Item::WOODEN_SHOVEL => 1,
1909  Item::GOLD_SHOVEL => 1,
1910  Item::STONE_SHOVEL => 2,
1911  Item::IRON_SHOVEL => 3,
1912  Item::DIAMOND_SHOVEL => 4,
1913  ];
1914 
1915  $damage = [
1916  EntityDamageEvent::MODIFIER_BASE => isset($damageTable[$item->getId()]) ? $damageTable[$item->getId()] : 1,
1917  ];
1918 
1919  if($this->distance($target) > 8){
1920  $cancelled = true;
1921  }elseif($target instanceof Player){
1922  if(($target->getGamemode() & 0x01) > 0){
1923  break;
1924  }elseif($this->server->getConfigBoolean("pvp") !== true or $this->server->getDifficulty() === 0){
1925  $cancelled = true;
1926  }
1927 
1928  $armorValues = [
1929  Item::LEATHER_CAP => 1,
1930  Item::LEATHER_TUNIC => 3,
1931  Item::LEATHER_PANTS => 2,
1932  Item::LEATHER_BOOTS => 1,
1933  Item::CHAIN_HELMET => 1,
1934  Item::CHAIN_CHESTPLATE => 5,
1935  Item::CHAIN_LEGGINGS => 4,
1936  Item::CHAIN_BOOTS => 1,
1937  Item::GOLD_HELMET => 1,
1938  Item::GOLD_CHESTPLATE => 5,
1939  Item::GOLD_LEGGINGS => 3,
1940  Item::GOLD_BOOTS => 1,
1941  Item::IRON_HELMET => 2,
1942  Item::IRON_CHESTPLATE => 6,
1943  Item::IRON_LEGGINGS => 5,
1944  Item::IRON_BOOTS => 2,
1945  Item::DIAMOND_HELMET => 3,
1946  Item::DIAMOND_CHESTPLATE => 8,
1947  Item::DIAMOND_LEGGINGS => 6,
1948  Item::DIAMOND_BOOTS => 3,
1949  ];
1950  $points = 0;
1951  foreach($target->getInventory()->getArmorContents() as $index => $i){
1952  if(isset($armorValues[$i->getId()])){
1953  $points += $armorValues[$i->getId()];
1954  }
1955  }
1956 
1957  $damage[EntityDamageEvent::MODIFIER_ARMOR] = -floor($damage[EntityDamageEvent::MODIFIER_BASE] * $points * 0.04);
1958  }
1959 
1960  $ev = new EntityDamageByEntityEvent($this, $target, EntityDamageEvent::CAUSE_ENTITY_ATTACK, $damage);
1961  if($cancelled){
1962  $ev->setCancelled();
1963  }
1964 
1965  $target->attack($ev->getFinalDamage(), $ev);
1966 
1967  if($ev->isCancelled()){
1968  if($item->isTool() and $this->isSurvival()){
1969  $this->inventory->sendContents($this);
1970  }
1971  break;
1972  }
1973 
1974  if($item->isTool() and $this->isSurvival()){
1975  if($item->useOn($target) and $item->getDamage() >= $item->getMaxDurability()){
1976  $this->inventory->setItemInHand(Item::get(Item::AIR, 0, 1), $this);
1977  }else{
1978  $this->inventory->setItemInHand($item, $this);
1979  }
1980  }
1981  }
1982 
1983 
1984  break;
1985  case ProtocolInfo::ANIMATE_PACKET:
1986  if($this->spawned === false or $this->dead === true){
1987  break;
1988  }
1989 
1990  $this->server->getPluginManager()->callEvent($ev = new PlayerAnimationEvent($this, $packet->action));
1991  if($ev->isCancelled()){
1992  break;
1993  }
1994 
1995  $pk = new AnimatePacket();
1996  $pk->eid = $this->getId();
1997  $pk->action = $ev->getAnimationType();
1998  Server::broadcastPacket($this->getViewers(), $pk);
1999  break;
2000  case ProtocolInfo::RESPAWN_PACKET:
2001  if($this->spawned === false or $this->dead === false){
2002  break;
2003  }
2004 
2005  $this->craftingType = 0;
2006 
2007  $this->server->getPluginManager()->callEvent($ev = new PlayerRespawnEvent($this, $this->getSpawn()));
2008 
2009  $this->teleport($ev->getRespawnPosition());
2010 
2011  $this->fireTicks = 0;
2012  $this->airTicks = 300;
2013  $this->deadTicks = 0;
2014  $this->noDamageTicks = 60;
2015 
2016  $this->setHealth(20);
2017  $this->dead = false;
2018 
2019  $this->sendMetadata($this->getViewers());
2020  $this->sendMetadata($this);
2021 
2022  $this->sendSettings();
2023  $this->inventory->sendContents($this);
2024  $this->inventory->sendArmorContents($this);
2025 
2026  $this->blocked = false;
2027 
2028  $this->spawnToAll();
2029  $this->scheduleUpdate();
2030  break;
2031  case ProtocolInfo::SET_HEALTH_PACKET: //Not used
2032  break;
2033  case ProtocolInfo::ENTITY_EVENT_PACKET:
2034  if($this->spawned === false or $this->blocked === true or $this->dead === true){
2035  break;
2036  }
2037  $this->craftingType = 0;
2038 
2039  if($this->inAction === true){
2040  $this->inAction = false;
2041  $this->sendMetadata($this->getViewers());
2042  }
2043  switch($packet->event){
2044  case 9: //Eating
2045  $items = [
2046  Item::APPLE => 4,
2047  Item::MUSHROOM_STEW => 10,
2048  Item::BEETROOT_SOUP => 10,
2049  Item::BREAD => 5,
2050  Item::RAW_PORKCHOP => 3,
2051  Item::COOKED_PORKCHOP => 8,
2052  Item::RAW_BEEF => 3,
2053  Item::STEAK => 8,
2054  Item::COOKED_CHICKEN => 6,
2055  Item::RAW_CHICKEN => 2,
2056  Item::MELON_SLICE => 2,
2057  Item::GOLDEN_APPLE => 10,
2058  Item::PUMPKIN_PIE => 8,
2059  Item::CARROT => 4,
2060  Item::POTATO => 1,
2061  Item::BAKED_POTATO => 6,
2062  //Item::COOKIE => 2,
2063  //Item::COOKED_FISH => 5,
2064  //Item::RAW_FISH => 2,
2065  ];
2066  $slot = $this->inventory->getItemInHand();
2067  if($this->getHealth() < 20 and isset($items[$slot->getId()])){
2068  $this->server->getPluginManager()->callEvent($ev = new PlayerItemConsumeEvent($this, $slot));
2069  if($ev->isCancelled()){
2070  $this->inventory->sendContents($this);
2071  break;
2072  }
2073 
2074  $pk = new EntityEventPacket();
2075  $pk->eid = 0;
2076  $pk->event = 9;
2077  $this->dataPacket($pk);
2078  $pk->eid = $this->getId();
2079  Server::broadcastPacket($this->getViewers(), $pk);
2080 
2081  $amount = $items[$slot->getId()];
2082  $this->server->getPluginManager()->callEvent($ev = new EntityRegainHealthEvent($this, $amount, EntityRegainHealthEvent::CAUSE_EATING));
2083  if(!$ev->isCancelled()){
2084  $this->heal($ev->getAmount(), $ev);
2085  }
2086 
2087  --$slot->count;
2088  $this->inventory->setItemInHand($slot, $this);
2089  if($slot->getId() === Item::MUSHROOM_STEW or $slot->getId() === Item::BEETROOT_SOUP){
2090  $this->inventory->addItem(Item::get(Item::BOWL, 0, 1), $this);
2091  }
2092  }
2093  break;
2094  }
2095  break;
2096  case ProtocolInfo::DROP_ITEM_PACKET:
2097  if($this->spawned === false or $this->blocked === true or $this->dead === true){
2098  break;
2099  }
2100  $packet->eid = $this->id;
2101  $item = $this->inventory->getItemInHand();
2102  $ev = new PlayerDropItemEvent($this, $item);
2103  $this->server->getPluginManager()->callEvent($ev);
2104  if($ev->isCancelled()){
2105  $this->inventory->sendContents($this);
2106  break;
2107  }
2108 
2109  $this->inventory->setItemInHand(Item::get(Item::AIR, 0, 1), $this);
2110  $motion = $this->getDirectionVector()->multiply(0.4);
2111 
2112  $this->level->dropItem($this->add(0, 1.3, 0), $item, $motion, 40);
2113 
2114  if($this->inAction === true){
2115  $this->inAction = false;
2116  $this->sendMetadata($this->getViewers());
2117  }
2118  break;
2119  case ProtocolInfo::MESSAGE_PACKET:
2120  if($this->spawned === false or $this->dead === true){
2121  break;
2122  }
2123  $this->craftingType = 0;
2124  $packet->message = TextFormat::clean($packet->message);
2125  if(trim($packet->message) != "" and strlen($packet->message) <= 255){
2126  $message = $packet->message;
2127  $this->server->getPluginManager()->callEvent($ev = new PlayerCommandPreprocessEvent($this, $message));
2128  if($ev->isCancelled()){
2129  break;
2130  }
2131  if(substr($ev->getMessage(), 0, 1) === "/"){ //Command
2132  Timings::$playerCommandTimer->startTiming();
2133  $this->server->dispatchCommand($ev->getPlayer(), substr($ev->getMessage(), 1));
2134  Timings::$playerCommandTimer->stopTiming();
2135  }else{
2136  $this->server->getPluginManager()->callEvent($ev = new PlayerChatEvent($this, $ev->getMessage()));
2137  if(!$ev->isCancelled()){
2138  $this->server->broadcastMessage(sprintf($ev->getFormat(), $ev->getPlayer()->getDisplayName(), $ev->getMessage()), $ev->getRecipients());
2139  }
2140  }
2141  }
2142  break;
2143  case ProtocolInfo::CONTAINER_CLOSE_PACKET:
2144  if($this->spawned === false or $packet->windowid === 0){
2145  break;
2146  }
2147  $this->craftingType = 0;
2148  $this->currentTransaction = null;
2149  if(isset($this->windowIndex[$packet->windowid])){
2150  $this->server->getPluginManager()->callEvent(new InventoryCloseEvent($this->windowIndex[$packet->windowid], $this));
2151  $this->removeWindow($this->windowIndex[$packet->windowid]);
2152  }else{
2153  unset($this->windowIndex[$packet->windowid]);
2154  }
2155  break;
2156  case ProtocolInfo::CONTAINER_SET_SLOT_PACKET:
2157  if($this->spawned === false or $this->blocked === true or $this->dead === true){
2158  break;
2159  }
2160 
2161  if($packet->slot < 0){
2162  break;
2163  }
2164 
2165  if($packet->windowid === 0){ //Our inventory
2166  if($packet->slot >= $this->inventory->getSize()){
2167  break;
2168  }
2169  if($this->isCreative()){
2170  if($this->getCreativeBlock($packet->item) !== -1){
2171  $this->inventory->setItem($packet->slot, $packet->item);
2172  $this->inventory->setHotbarSlotIndex($packet->slot, $packet->slot); //links $hotbar[$packet->slot] to $slots[$packet->slot]
2173  }
2174  }
2175  $transaction = new BaseTransaction($this->inventory, $packet->slot, $this->inventory->getItem($packet->slot), $packet->item);
2176  }elseif($packet->windowid === 0x78){ //Our armor
2177  if($packet->slot >= 4){
2178  break;
2179  }
2180 
2181  $transaction = new BaseTransaction($this->inventory, $packet->slot + $this->inventory->getSize(), $this->inventory->getArmorItem($packet->slot), $packet->item);
2182  }elseif(isset($this->windowIndex[$packet->windowid])){
2183  $this->craftingType = 0;
2184  $inv = $this->windowIndex[$packet->windowid];
2185  $transaction = new BaseTransaction($inv, $packet->slot, $inv->getItem($packet->slot), $packet->item);
2186  }else{
2187  break;
2188  }
2189 
2190  if($transaction->getSourceItem()->equals($transaction->getTargetItem(), true) and $transaction->getTargetItem()->getCount() === $transaction->getSourceItem()->getCount()){ //No changes!
2191  //No changes, just a local inventory update sent by the server
2192  break;
2193  }
2194 
2195 
2196  if($this->currentTransaction === null or $this->currentTransaction->getCreationTime() < (microtime(true) - 8)){
2197  if($this->currentTransaction instanceof SimpleTransactionGroup){
2198  foreach($this->currentTransaction->getInventories() as $inventory){
2199  if($inventory instanceof PlayerInventory){
2200  $inventory->sendArmorContents($this);
2201  }
2202  $inventory->sendContents($this);
2203  }
2204  }
2205  $this->currentTransaction = new SimpleTransactionGroup($this);
2206  }
2207 
2208  $this->currentTransaction->addTransaction($transaction);
2209 
2210  if($this->currentTransaction->canExecute()){
2211  if($this->currentTransaction->execute()){
2212  foreach($this->currentTransaction->getTransactions() as $ts){
2213  $inv = $ts->getInventory();
2214  if($inv instanceof FurnaceInventory){
2215  if($ts->getSlot() === 2){
2216  switch($inv->getResult()->getId()){
2217  case Item::IRON_INGOT:
2218  $this->awardAchievement("acquireIron");
2219  break;
2220  }
2221  }
2222  }
2223  }
2224  }
2225 
2226  $this->currentTransaction = null;
2227  }elseif($packet->windowid == 0){ //Try crafting
2228  $craftingGroup = new CraftingTransactionGroup($this->currentTransaction);
2229  if($craftingGroup->canExecute()){ //We can craft!
2230  $recipe = $craftingGroup->getMatchingRecipe();
2231  if($recipe instanceof BigShapelessRecipe and $this->craftingType !== 1){
2232  break;
2233  }elseif($recipe instanceof StonecutterShapelessRecipe and $this->craftingType !== 2){
2234  break;
2235  }
2236 
2237  if($craftingGroup->execute()){
2238  switch($craftingGroup->getResult()->getId()){
2239  case Item::WORKBENCH:
2240  $this->awardAchievement("buildWorkBench");
2241  break;
2242  case Item::WOODEN_PICKAXE:
2243  $this->awardAchievement("buildPickaxe");
2244  break;
2245  case Item::FURNACE:
2246  $this->awardAchievement("buildFurnace");
2247  break;
2248  case Item::WOODEN_HOE:
2249  $this->awardAchievement("buildHoe");
2250  break;
2251  case Item::BREAD:
2252  $this->awardAchievement("makeBread");
2253  break;
2254  case Item::CAKE:
2255  //TODO: detect complex recipes like cake that leave remains
2256  $this->awardAchievement("bakeCake");
2257  $this->inventory->addItem(Item::get(Item::BUCKET, 0, 3), $this);
2258  break;
2259  case Item::STONE_PICKAXE:
2260  case Item::GOLD_PICKAXE:
2261  case Item::IRON_PICKAXE:
2262  case Item::DIAMOND_PICKAXE:
2263  $this->awardAchievement("buildBetterPickaxe");
2264  break;
2265  case Item::WOODEN_SWORD:
2266  $this->awardAchievement("buildSword");
2267  break;
2268  case Item::DIAMOND:
2269  $this->awardAchievement("diamond");
2270  break;
2271  }
2272  }
2273 
2274 
2275  $this->currentTransaction = null;
2276  }
2277 
2278 
2279  }
2280 
2281 
2282  break;
2283  case ProtocolInfo::SEND_INVENTORY_PACKET: //TODO, Mojang, enable this ยด^_^`
2284  if($this->spawned === false){
2285  break;
2286  }
2287  break;
2288  case ProtocolInfo::ENTITY_DATA_PACKET:
2289  if($this->spawned === false or $this->blocked === true or $this->dead === true){
2290  break;
2291  }
2292  $this->craftingType = 0;
2293 
2294  $t = $this->level->getTile(new Vector3($packet->x, $packet->y, $packet->z));
2295  if($t instanceof Sign){
2296  $nbt = new NBT(NBT::LITTLE_ENDIAN);
2297  $nbt->read($packet->namedtag);
2298  $nbt = $nbt->getData();
2299  if($nbt["id"] !== Tile::SIGN){
2300  $t->spawnTo($this);
2301  }else{
2302  $ev = new SignChangeEvent($t->getBlock(), $this, [
2303  $nbt["Text1"], $nbt["Text2"], $nbt["Text3"], $nbt["Text4"]
2304  ]);
2305 
2306  if(!isset($t->namedtag->Creator) or $t->namedtag["Creator"] !== $this->username){
2307  $ev->setCancelled(true);
2308  }
2309 
2310  $this->server->getPluginManager()->callEvent($ev);
2311 
2312  if(!$ev->isCancelled()){
2313  $t->setText($ev->getLine(0), $ev->getLine(1), $ev->getLine(2), $ev->getLine(3));
2314  }else{
2315  $t->spawnTo($this);
2316  }
2317  }
2318  }
2319  break;
2320  default:
2321  break;
2322  }
2323  }
2324 
2332  public function kick($reason = ""){
2333  $this->server->getPluginManager()->callEvent($ev = new PlayerKickEvent($this, $reason, TextFormat::YELLOW . $this->username . " has left the game"));
2334  if(!$ev->isCancelled()){
2335  $message = "Kicked by admin." . ($reason !== "" ? " Reason: " . $reason : "");
2336  $this->sendMessage($message);
2337  $this->close($ev->getQuitMessage(), $message);
2338 
2339  return true;
2340  }
2341 
2342  return false;
2343  }
2344 
2350  public function sendMessage($message){
2351  if($this->removeFormat !== false){
2352  $message = TextWrapper::wrap(TextFormat::clean($message));
2353  }
2354  $mes = explode("\n", $message);
2355  foreach($mes as $m){
2356  if($m !== ""){
2357  $pk = new MessagePacket();
2358  $pk->source = ""; //Do not use this ;)
2359  $pk->message = $m;
2360  $this->dataPacket($pk);
2361  }
2362  }
2363  }
2364 
2369  public function close($message = "", $reason = "generic reason"){
2370 
2371  foreach($this->tasks as $task){
2372  $task->cancel();
2373  }
2374  $this->tasks = [];
2375 
2376  if($this->connected and !$this->closed){
2377  $this->connected = false;
2378  if($this->username != ""){
2379  $this->server->getPluginManager()->callEvent($ev = new PlayerQuitEvent($this, $message));
2380  if($this->server->getAutoSave() and $this->loggedIn === true){
2381  $this->save();
2382  }
2383  }
2384 
2385  foreach($this->server->getOnlinePlayers() as $player){
2386  if(!$player->canSee($this)){
2387  $player->showPlayer($this);
2388  }
2389  }
2390  $this->hiddenPlayers = [];
2391 
2392  foreach($this->windowIndex as $window){
2393  $this->removeWindow($window);
2394  }
2395 
2396  $this->interface->close($this, $reason);
2397 
2398  $chunkX = $chunkZ = null;
2399  foreach($this->usedChunks as $index => $d){
2400  Level::getXZ($index, $chunkX, $chunkZ);
2401  $this->level->freeChunk($chunkX, $chunkZ, $this);
2402  unset($this->usedChunks[$index]);
2403  }
2404 
2405  parent::close();
2406 
2407  $this->loggedIn = false;
2408 
2409  if(isset($ev) and $this->username != "" and $this->spawned !== false and $ev->getQuitMessage() != ""){
2410  $this->server->broadcastMessage($ev->getQuitMessage());
2411  }
2412 
2413  $this->server->getPluginManager()->unsubscribeFromPermission(Server::BROADCAST_CHANNEL_USERS, $this);
2414  $this->spawned = false;
2415  $this->server->getLogger()->info(TextFormat::AQUA . $this->username . TextFormat::WHITE . "[/" . $this->ip . ":" . $this->port . "] logged out due to " . str_replace(["\n", "\r"], [" ", ""], $reason));
2416  $this->windows = new \SplObjectStorage();
2417  $this->windowIndex = [];
2418  $this->usedChunks = [];
2419  $this->loadQueue = [];
2420  $this->hasSpawned = [];
2421  $this->spawnPosition = null;
2422  unset($this->buffer);
2423  }
2424 
2425  $this->perm->clearPermissions();
2426  $this->server->removePlayer($this);
2427  }
2428 
2429  public function __debugInfo(){
2430  return [];
2431  }
2432 
2436  public function save(){
2437  if($this->closed){
2438  throw new \InvalidStateException("Tried to save closed player");
2439  }
2440 
2441  parent::saveNBT();
2442  if($this->level instanceof Level){
2443  $this->namedtag->Level = new String("Level", $this->level->getName());
2444  if($this->spawnPosition instanceof Position and $this->spawnPosition->getLevel() instanceof Level){
2445  $this->namedtag["SpawnLevel"] = $this->spawnPosition->getLevel()->getName();
2446  $this->namedtag["SpawnX"] = (int) $this->spawnPosition->x;
2447  $this->namedtag["SpawnY"] = (int) $this->spawnPosition->y;
2448  $this->namedtag["SpawnZ"] = (int) $this->spawnPosition->z;
2449  }
2450 
2451  foreach($this->achievements as $achievement => $status){
2452  $this->namedtag->Achievements[$achievement] = new Byte($achievement, $status === true ? 1 : 0);
2453  }
2454 
2455  $this->namedtag["playerGameType"] = $this->gamemode;
2456  $this->namedtag["lastPlayed"] = floor(microtime(true) * 1000);
2457 
2458  if($this->username != "" and $this->namedtag instanceof Compound){
2459  $this->server->saveOfflinePlayerData($this->username, $this->namedtag);
2460  }
2461  }
2462  }
2463 
2469  public function getName(){
2470  return $this->username;
2471  }
2472 
2473  public function kill(){
2474  if($this->dead === true or $this->spawned === false){
2475  return;
2476  }
2477 
2478  $message = $this->getName() . " died";
2479  $cause = $this->getLastDamageCause();
2480  $ev = null;
2481  if($cause instanceof EntityDamageEvent){
2482  $ev = $cause;
2483  $cause = $ev->getCause();
2484  }
2485 
2486  switch($cause){
2487  case EntityDamageEvent::CAUSE_ENTITY_ATTACK:
2488  if($ev instanceof EntityDamageByEntityEvent){
2489  $e = $ev->getDamager();
2490  if($e instanceof Player){
2491  $message = $this->getName() . " was killed by " . $e->getName();
2492  break;
2493  }elseif($e instanceof Living){
2494  $message = $this->getName() . " was slain by " . $e->getName();
2495  break;
2496  }
2497  }
2498  $message = $this->getName() . " was killed";
2499  break;
2500  case EntityDamageEvent::CAUSE_PROJECTILE:
2501  if($ev instanceof EntityDamageByEntityEvent){
2502  $e = $ev->getDamager();
2503  if($e instanceof Living){
2504  $message = $this->getName() . " was shot by " . $e->getName();
2505  break;
2506  }
2507  }
2508  $message = $this->getName() . " was shot by arrow";
2509  break;
2510  case EntityDamageEvent::CAUSE_SUICIDE:
2511  $message = $this->getName() . " died";
2512  break;
2513  case EntityDamageEvent::CAUSE_VOID:
2514  $message = $this->getName() . " fell out of the world";
2515  break;
2516  case EntityDamageEvent::CAUSE_FALL:
2517  if($ev instanceof EntityDamageEvent){
2518  if($ev->getFinalDamage() > 2){
2519  $message = $this->getName() . " fell from a high place";
2520  break;
2521  }
2522  }
2523  $message = $this->getName() . " hit the ground too hard";
2524  break;
2525 
2526  case EntityDamageEvent::CAUSE_SUFFOCATION:
2527  $message = $this->getName() . " suffocated in a wall";
2528  break;
2529 
2530  case EntityDamageEvent::CAUSE_LAVA:
2531  $message = $this->getName() . " tried to swim in lava";
2532  break;
2533 
2534  case EntityDamageEvent::CAUSE_FIRE:
2535  $message = $this->getName() . " went up in flames";
2536  break;
2537 
2538  case EntityDamageEvent::CAUSE_FIRE_TICK:
2539  $message = $this->getName() . " burned to death";
2540  break;
2541 
2542  case EntityDamageEvent::CAUSE_DROWNING:
2543  $message = $this->getName() . " drowned";
2544  break;
2545 
2546  case EntityDamageEvent::CAUSE_CONTACT:
2547  $message = $this->getName() . " was pricked to death";
2548  break;
2549 
2550  case EntityDamageEvent::CAUSE_BLOCK_EXPLOSION:
2551  case EntityDamageEvent::CAUSE_ENTITY_EXPLOSION:
2552  $message = $this->getName() . " blew up";
2553  break;
2554 
2555  case EntityDamageEvent::CAUSE_MAGIC:
2556  case EntityDamageEvent::CAUSE_CUSTOM:
2557 
2558  default:
2559 
2560  }
2561 
2562  if($this->dead){
2563  return;
2564  }
2565 
2566  Entity::kill();
2567 
2568  $this->server->getPluginManager()->callEvent($ev = new PlayerDeathEvent($this, $this->getDrops(), $message));
2569 
2570  if(!$ev->getKeepInventory()){
2571  foreach($ev->getDrops() as $item){
2572  $this->level->dropItem($this, $item);
2573  }
2574 
2575  if($this->inventory !== null){
2576  $this->inventory->clearAll();
2577  }
2578  }
2579 
2580  if($ev->getDeathMessage() != ""){
2581  $this->server->broadcast($ev->getDeathMessage(), Server::BROADCAST_CHANNEL_USERS);
2582  }
2583  }
2584 
2585  public function setHealth($amount){
2586  parent::setHealth($amount);
2587  if($this->spawned === true){
2588  $pk = new SetHealthPacket();
2589  $pk->health = $this->getHealth();
2590  $this->dataPacket($pk);
2591  }
2592  }
2593 
2594  public function attack($damage, $source = EntityDamageEvent::CAUSE_MAGIC){
2595  if($this->dead === true){
2596  return;
2597  }
2598 
2599  if($this->isCreative()){
2600  if($source instanceof EntityDamageEvent){
2601  $cause = $source->getCause();
2602  }else{
2603  $cause = $source;
2604  }
2605 
2606  if(
2607  $cause !== EntityDamageEvent::CAUSE_MAGIC
2608  and $cause !== EntityDamageEvent::CAUSE_SUICIDE
2609  and $cause !== EntityDamageEvent::CAUSE_VOID
2610  ){
2611  if($source instanceof EntityDamageEvent){
2612  $source->setCancelled();
2613  }
2614  return;
2615  }
2616  }
2617 
2618 
2619  parent::attack($damage, $source);
2620 
2621  if($source instanceof EntityDamageEvent and $source->isCancelled()){
2622  return;
2623  }
2624 
2625  if($this->getLastDamageCause() === $source){
2626  $pk = new EntityEventPacket();
2627  $pk->eid = 0;
2628  $pk->event = 2;
2629  $this->dataPacket($pk);
2630  }
2631  }
2632 
2633  public function getData(){ //TODO
2634  $flags = 0;
2635  $flags |= $this->fireTicks > 0 ? 1 : 0;
2636  //$flags |= ($this->crouched === true ? 0b10:0) << 1;
2637  $flags |= ($this->inAction === true ? 0b10000 : 0);
2638  $d = [
2639  0 => ["type" => 0, "value" => $flags],
2640  1 => ["type" => 1, "value" => $this->airTicks],
2641  16 => ["type" => 0, "value" => 0],
2642  17 => ["type" => 6, "value" => [0, 0, 0]],
2643  ];
2644 
2645 
2646  if($this->sleeping instanceof Vector3){
2647  $d[16]["value"] = 2;
2648  $d[17]["value"] = [$this->sleeping->x, $this->sleeping->y, $this->sleeping->z];
2649  }
2650 
2651 
2652  return $d;
2653  }
2654 
2655  public function teleport(Vector3 $pos, $yaw = null, $pitch = null){
2656  if(parent::teleport($pos, $yaw, $pitch)){
2657 
2658  foreach($this->windowIndex as $window){
2659  if($window === $this->inventory){
2660  continue;
2661  }
2662  $this->removeWindow($window);
2663  }
2664 
2665  $this->airTicks = 300;
2666  $this->fallDistance = 0;
2667  $this->orderChunks();
2668  $this->nextChunkOrderRun = 0;
2669  $this->forceMovement = new Vector3($this->x, $this->y, $this->z);
2670  $this->newPosition = null;
2671 
2672  $pk = new MovePlayerPacket();
2673  $pk->eid = 0;
2674  $pk->x = $this->x;
2675  $pk->y = $this->y + $this->getEyeHeight();
2676  $pk->z = $this->z;
2677  $pk->bodyYaw = $this->yaw;
2678  $pk->pitch = $this->pitch;
2679  $pk->yaw = $this->yaw;
2680  $pk->teleport = true;
2681  $this->directDataPacket($pk);
2682  }
2683  }
2684 
2685 
2691  public function getWindowId(Inventory $inventory){
2692  if($this->windows->contains($inventory)){
2693  return $this->windows[$inventory];
2694  }
2695 
2696  return -1;
2697  }
2698 
2707  public function addWindow(Inventory $inventory, $forceId = null){
2708  if($this->windows->contains($inventory)){
2709  return $this->windows[$inventory];
2710  }
2711 
2712  if($forceId === null){
2713  $this->windowCnt = $cnt = max(2, ++$this->windowCnt % 99);
2714  }else{
2715  $cnt = (int) $forceId;
2716  }
2717  $this->windowIndex[$cnt] = $inventory;
2718  $this->windows->attach($inventory, $cnt);
2719  if($inventory->open($this)){
2720  return $cnt;
2721  }else{
2722  $this->removeWindow($inventory);
2723 
2724  return -1;
2725  }
2726  }
2727 
2728  public function removeWindow(Inventory $inventory){
2729  $inventory->close($this);
2730  if($this->windows->contains($inventory)){
2731  $id = $this->windows[$inventory];
2732  $this->windows->detach($this->windowIndex[$id]);
2733  unset($this->windowIndex[$id]);
2734  }
2735  }
2736 
2737  public function setMetadata($metadataKey, MetadataValue $metadataValue){
2738  $this->server->getPlayerMetadata()->setMetadata($this, $metadataKey, $metadataValue);
2739  }
2740 
2741  public function getMetadata($metadataKey){
2742  return $this->server->getPlayerMetadata()->getMetadata($this, $metadataKey);
2743  }
2744 
2745  public function hasMetadata($metadataKey){
2746  return $this->server->getPlayerMetadata()->hasMetadata($this, $metadataKey);
2747  }
2748 
2749  public function removeMetadata($metadataKey, Plugin $plugin){
2750  $this->server->getPlayerMetadata()->removeMetadata($this, $metadataKey, $plugin);
2751  }
2752 
2753 
2754 }
setDisplayName($name)
Definition: Player.php:478
showPlayer(Player $player)
Definition: Player.php:303
addAttachment(Plugin $plugin, $name=null, $value=null)
Definition: Player.php:373
removeAchievement($achievementId)
Definition: Player.php:434
sendSettings($nametags=true)
Definition: Player.php:975
dataPacket(DataPacket $packet, $needACK=false)
Definition: Player.php:745
__construct(SourceInterface $interface, $clientID, $ip, $port)
Definition: Player.php:412
hasAchievement($achievementId)
Definition: Player.php:445
setMetadata($metadataKey, MetadataValue $metadataValue)
Definition: Player.php:2737
hasMetadata($metadataKey)
Definition: Player.php:2745
getMetadata($metadataKey)
Definition: Player.php:2741
getWindowId(Inventory $inventory)
Definition: Player.php:2691
removeAttachment(PermissionAttachment $attachment)
Definition: Player.php:380
sleepOn(Vector3 $pos)
Definition: Player.php:796
recalculatePermissions()
Definition: Player.php:384
isPermissionSet($name)
Definition: Player.php:353
awardAchievement($achievementId)
Definition: Player.php:889
removeMetadata($metadataKey, Plugin $plugin)
Definition: Player.php:2749
setNameTag($name)
Definition: Player.php:492
setRemoveFormat($remove=true)
Definition: Player.php:276
addWindow(Inventory $inventory, $forceId=null)
Definition: Player.php:2707
hidePlayer(Player $player)
Definition: Player.php:292
setSpawn(Vector3 $pos)
Definition: Player.php:830
sendMessage($message)
Definition: Player.php:2350
checkACK($identifier)
Definition: Player.php:556
hasPermission($name)
Definition: Player.php:362
getEffectivePermissions()
Definition: Player.php:401
setWhitelisted($value)
Definition: Player.php:222
canSee(Player $player)
Definition: Player.php:285
close($message="", $reason="generic reason")
Definition: Player.php:2369
kick($reason="")
Definition: Player.php:2332
spawnTo(Player $player)
Definition: Player.php:253
directDataPacket(DataPacket $packet, $needACK=false)
Definition: Player.php:771
setOp($value)
Definition: Player.php:334
setBanned($value)
Definition: Player.php:210