PocketMine-MP  1.4 - API 1.10.0
 All Classes Namespaces Functions Variables Pages
Server.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 
26 namespace pocketmine;
27 
38 use pocketmine\entity\Item as DroppedItem;
106 
110 class Server{
111  const BROADCAST_CHANNEL_ADMINISTRATIVE = "pocketmine.broadcast.admin";
112  const BROADCAST_CHANNEL_USERS = "pocketmine.broadcast.user";
113 
115  private static $instance = null;
116 
118  private $banByName = null;
119 
121  private $banByIP = null;
122 
124  private $operators = null;
125 
127  private $whitelist = null;
128 
130  private $isRunning = true;
131 
132  private $hasStopped = false;
133 
135  private $pluginManager = null;
136 
138  private $updater = null;
139 
141  private $scheduler = null;
142 
144  private $generationManager = null;
145 
151  private $tickCounter;
152  private $nextTick = 0;
153  private $tickAverage = [20, 20, 20, 20, 20];
154  private $useAverage = [20, 20, 20, 20, 20];
155 
157  private $logger;
158 
160  private $console = null;
161  private $consoleThreaded;
162 
164  private $commandMap = null;
165 
167  private $craftingManager;
168 
170  private $consoleSender;
171 
173  private $maxPlayers;
174 
176  private $autoSave;
177 
179  private $rcon;
180 
182  private $entityMetadata;
183 
185  private $playerMetadata;
186 
188  private $levelMetadata;
189 
191  private $interfaces = [];
193  private $mainInterface;
194 
195  private $serverID;
196 
197  private $autoloader;
198  private $filePath;
199  private $dataPath;
200  private $pluginPath;
201 
202  private $lastSendUsage = null;
203 
205  private $queryHandler;
206 
208  private $properties;
209 
211  private $config;
212 
214  private $players = [];
215 
217  private $levels = [];
218 
220  private $levelDefault = null;
221 
225  public function getName(){
226  return "PocketMine-MP";
227  }
228 
232  public function isRunning(){
233  return $this->isRunning === true;
234  }
235 
239  public function getPocketMineVersion(){
240  return \pocketmine\VERSION;
241  }
242 
246  public function getCodename(){
247  return \pocketmine\CODENAME;
248  }
249 
253  public function getVersion(){
254  return \pocketmine\MINECRAFT_VERSION;
255  }
256 
260  public function getApiVersion(){
261  return \pocketmine\API_VERSION;
262  }
263 
267  public function getFilePath(){
268  return $this->filePath;
269  }
270 
274  public function getDataPath(){
275  return $this->dataPath;
276  }
277 
281  public function getPluginPath(){
282  return $this->pluginPath;
283  }
284 
288  public function getMaxPlayers(){
289  return $this->maxPlayers;
290  }
291 
295  public function getPort(){
296  return $this->getConfigInt("server-port", 19132);
297  }
298 
302  public function getViewDistance(){
303  return max(56, $this->getProperty("chunk-sending.max-chunks", 256));
304  }
305 
309  public function getIp(){
310  return $this->getConfigString("server-ip", "0.0.0.0");
311  }
312 
316  public function getServerName(){
317  return $this->getConfigString("motd", "Minecraft: PE Server");
318  }
319 
323  public function getAutoSave(){
324  return $this->autoSave;
325  }
326 
330  public function setAutoSave($value){
331  $this->autoSave = (bool) $value;
332  foreach($this->getLevels() as $level){
333  $level->setAutoSave($this->autoSave);
334  }
335  }
336 
340  public function getLevelType(){
341  return $this->getConfigString("level-type", "DEFAULT");
342  }
343 
347  public function getGenerateStructures(){
348  return $this->getConfigBoolean("generate-structures", true);
349  }
350 
354  public function getGamemode(){
355  return $this->getConfigInt("gamemode", 0) & 0b11;
356  }
357 
361  public function getForceGamemode(){
362  return $this->getConfigBoolean("force-gamemode", false);
363  }
364 
372  public static function getGamemodeString($mode){
373  switch((int) $mode){
374  case Player::SURVIVAL:
375  return "SURVIVAL";
376  case Player::CREATIVE:
377  return "CREATIVE";
378  case Player::ADVENTURE:
379  return "ADVENTURE";
380  case Player::SPECTATOR:
381  return "SPECTATOR";
382  }
383 
384  return "UNKNOWN";
385  }
386 
394  public static function getGamemodeFromString($str){
395  switch(strtolower(trim($str))){
396  case (string) Player::SURVIVAL:
397  case "survival":
398  case "s":
399  return Player::SURVIVAL;
400 
401  case (string) Player::CREATIVE:
402  case "creative":
403  case "c":
404  return Player::CREATIVE;
405 
406  case (string) Player::ADVENTURE:
407  case "adventure":
408  case "a":
409  return Player::ADVENTURE;
410 
411  case (string) Player::SPECTATOR:
412  case "spectator":
413  case "view":
414  case "v":
415  return Player::SPECTATOR;
416  }
417  return -1;
418  }
419 
425  public static function getDifficultyFromString($str){
426  switch(strtolower(trim($str))){
427  case "0":
428  case "peaceful":
429  case "p":
430  return 0;
431 
432  case "1":
433  case "easy":
434  case "e":
435  return 1;
436 
437  case "2":
438  case "normal":
439  case "n":
440  return 2;
441 
442  case "3":
443  case "hard":
444  case "h":
445  return 3;
446  }
447  return -1;
448  }
449 
453  public function getDifficulty(){
454  return $this->getConfigInt("difficulty", 1);
455  }
456 
460  public function hasWhitelist(){
461  return $this->getConfigBoolean("white-list", false);
462  }
463 
467  public function getSpawnRadius(){
468  return $this->getConfigInt("spawn-protection", 16);
469  }
470 
474  public function getAllowFlight(){
475  return $this->getConfigBoolean("allow-flight", false);
476  }
477 
481  public function isHardcore(){
482  return $this->getConfigBoolean("hardcore", false);
483  }
484 
488  public function getDefaultGamemode(){
489  return $this->getConfigInt("gamemode", 0) & 0b11;
490  }
491 
495  public function getMotd(){
496  return $this->getConfigString("motd", "Minecraft: PE Server");
497  }
498 
502  public function getLoader(){
503  return $this->autoloader;
504  }
505 
509  public function getLogger(){
510  return $this->logger;
511  }
512 
516  public function getEntityMetadata(){
517  return $this->entityMetadata;
518  }
519 
523  public function getPlayerMetadata(){
524  return $this->playerMetadata;
525  }
526 
530  public function getLevelMetadata(){
531  return $this->levelMetadata;
532  }
533 
537  public function getUpdater(){
538  return $this->updater;
539  }
540 
544  public function getPluginManager(){
545  return $this->pluginManager;
546  }
547 
551  public function getCraftingManager(){
552  return $this->craftingManager;
553  }
554 
558  public function getScheduler(){
559  return $this->scheduler;
560  }
561 
565  public function getGenerationManager(){
566  return $this->generationManager;
567  }
568 
572  public function getTick(){
573  return $this->tickCounter;
574  }
575 
581  public function getTicksPerSecond(){
582  return round(array_sum($this->tickAverage) / count($this->tickAverage), 2);
583  }
584 
590  public function getTickUsage(){
591  return round((array_sum($this->useAverage) / count($this->useAverage)) * 100, 2);
592  }
593 
597  public function getInterfaces(){
598  return $this->interfaces;
599  }
600 
604  public function addInterface(SourceInterface $interface){
605  $this->interfaces[spl_object_hash($interface)] = $interface;
606  }
607 
611  public function removeInterface(SourceInterface $interface){
612  $interface->shutdown();
613  unset($this->interfaces[spl_object_hash($interface)]);
614  }
615 
621  public function sendPacket($address, $port, $payload){
622  $this->mainInterface->putRaw($address, $port, $payload);
623  }
624 
631  public function blockAddress($address, $timeout = 300){
632  $this->mainInterface->blockAddress($address, $timeout);
633  }
634 
640  public function handlePacket($address, $port, $payload){
641  try{
642  if(strlen($payload) > 2 and substr($payload, 0, 2) === "\xfe\xfd" and $this->queryHandler instanceof QueryHandler){
643  $this->queryHandler->handle($address, $port, $payload);
644  }
645  }catch(\Exception $e){
646  if(\pocketmine\DEBUG > 1){
647  if($this->logger instanceof MainLogger){
648  $this->logger->logException($e);
649  }
650  }
651 
652  $this->blockAddress($address, 600);
653  }
654  //TODO: add raw packet events
655  }
656 
660  public function getCommandMap(){
661  return $this->commandMap;
662  }
663 
667  public function getOnlinePlayers(){
668  return $this->players;
669  }
670 
671  public function addRecipe(Recipe $recipe){
672  $this->craftingManager->registerRecipe($recipe);
673  }
674 
680  public function getOfflinePlayer($name){
681  $name = strtolower($name);
682  $result = $this->getPlayerExact($name);
683 
684  if($result === null){
685  return new OfflinePlayer($this, $name);
686  }
687 
688  return $result;
689  }
690 
696  public function getOfflinePlayerData($name){
697  $name = strtolower($name);
698  $path = $this->getDataPath() . "players/";
699  if(file_exists($path . "$name.dat")){
700  try{
701  $nbt = new NBT(NBT::BIG_ENDIAN);
702  $nbt->readCompressed(file_get_contents($path . "$name.dat"));
703 
704  return $nbt->getData();
705  }catch(\Exception $e){ //zlib decode error / corrupt data
706  rename($path . "$name.dat", $path . "$name.dat.bak");
707  $this->logger->warning("Corrupted data found for \"" . $name . "\", creating new profile");
708  }
709  }else{
710  $this->logger->notice("Player data not found for \"" . $name . "\", creating new profile");
711  }
712  $spawn = $this->getDefaultLevel()->getSafeSpawn();
713  $nbt = new Compound(false, [
714  new Long("firstPlayed", floor(microtime(true) * 1000)),
715  new Long("lastPlayed", floor(microtime(true) * 1000)),
716  new Enum("Pos", [
717  new Double(0, $spawn->x),
718  new Double(1, $spawn->y),
719  new Double(2, $spawn->z)
720  ]),
721  new String("Level", $this->getDefaultLevel()->getName()),
722  //new String("SpawnLevel", $this->getDefaultLevel()->getName()),
723  //new Int("SpawnX", (int) $spawn->x),
724  //new Int("SpawnY", (int) $spawn->y),
725  //new Int("SpawnZ", (int) $spawn->z),
726  //new Byte("SpawnForced", 1), //TODO
727  new Enum("Inventory", []),
728  new Compound("Achievements", []),
729  new Int("playerGameType", $this->getGamemode()),
730  new Enum("Motion", [
731  new Double(0, 0.0),
732  new Double(1, 0.0),
733  new Double(2, 0.0)
734  ]),
735  new Enum("Rotation", [
736  new Float(0, 0.0),
737  new Float(1, 0.0)
738  ]),
739  new Float("FallDistance", 0.0),
740  new Short("Fire", 0),
741  new Short("Air", 0),
742  new Byte("OnGround", 1),
743  new Byte("Invulnerable", 0),
744  new String("NameTag", $name),
745  ]);
746  $nbt->Pos->setTagType(NBT::TAG_Double);
747  $nbt->Inventory->setTagType(NBT::TAG_Compound);
748  $nbt->Motion->setTagType(NBT::TAG_Double);
749  $nbt->Rotation->setTagType(NBT::TAG_Float);
750 
751  if(file_exists($path . "$name.yml")){ //Importing old PocketMine-MP files
752  $data = new Config($path . "$name.yml", Config::YAML, []);
753  $nbt["playerGameType"] = (int) $data->get("gamemode");
754  $nbt["Level"] = $data->get("position")["level"];
755  $nbt["Pos"][0] = $data->get("position")["x"];
756  $nbt["Pos"][1] = $data->get("position")["y"];
757  $nbt["Pos"][2] = $data->get("position")["z"];
758  $nbt["SpawnLevel"] = $data->get("spawn")["level"];
759  $nbt["SpawnX"] = (int) $data->get("spawn")["x"];
760  $nbt["SpawnY"] = (int) $data->get("spawn")["y"];
761  $nbt["SpawnZ"] = (int) $data->get("spawn")["z"];
762  $this->logger->notice("Old Player data found for \"" . $name . "\", upgrading profile");
763  foreach($data->get("inventory") as $slot => $item){
764  if(count($item) === 3){
765  $nbt->Inventory[$slot + 9] = new Compound(false, [
766  new Short("id", $item[0]),
767  new Short("Damage", $item[1]),
768  new Byte("Count", $item[2]),
769  new Byte("Slot", $slot + 9),
770  new Byte("TrueSlot", $slot + 9)
771  ]);
772  }
773  }
774  foreach($data->get("hotbar") as $slot => $itemSlot){
775  if(isset($nbt->Inventory[$itemSlot + 9])){
776  $item = $nbt->Inventory[$itemSlot + 9];
777  $nbt->Inventory[$slot] = new Compound(false, [
778  new Short("id", $item["id"]),
779  new Short("Damage", $item["Damage"]),
780  new Byte("Count", $item["Count"]),
781  new Byte("Slot", $slot),
782  new Byte("TrueSlot", $item["TrueSlot"])
783  ]);
784  }
785  }
786  foreach($data->get("armor") as $slot => $item){
787  if(count($item) === 2){
788  $nbt->Inventory[$slot + 100] = new Compound(false, [
789  new Short("id", $item[0]),
790  new Short("Damage", $item[1]),
791  new Byte("Count", 1),
792  new Byte("Slot", $slot + 100)
793  ]);
794  }
795  }
796  foreach($data->get("achievements") as $achievement => $status){
797  $nbt->Achievements[$achievement] = new Byte($achievement, $status == true ? 1 : 0);
798  }
799  unlink($path . "$name.yml");
800  }
801  $this->saveOfflinePlayerData($name, $nbt);
802 
803  return $nbt;
804 
805  }
806 
811  public function saveOfflinePlayerData($name, Compound $nbtTag){
812  $nbt = new NBT(NBT::BIG_ENDIAN);
813  $nbt->setData($nbtTag);
814  file_put_contents($this->getDataPath() . "players/" . strtolower($name) . ".dat", $nbt->writeCompressed());
815  }
816 
822  public function getPlayer($name){
823  $found = null;
824  $name = strtolower($name);
825  $delta = PHP_INT_MAX;
826  foreach($this->getOnlinePlayers() as $player){
827  if(stripos($player->getName(), $name) === 0){
828  $curDelta = strlen($player->getName()) - strlen($name);
829  if($curDelta < $delta){
830  $found = $player;
831  $delta = $curDelta;
832  }
833  if($curDelta === 0){
834  break;
835  }
836  }
837  }
838 
839  return $found;
840  }
841 
847  public function getPlayerExact($name){
848  $name = strtolower($name);
849  foreach($this->getOnlinePlayers() as $player){
850  if(strtolower($player->getName()) === $name){
851  return $player;
852  }
853  }
854 
855  return null;
856  }
857 
863  public function matchPlayer($partialName){
864  $partialName = strtolower($partialName);
865  $matchedPlayers = [];
866  foreach($this->getOnlinePlayers() as $player){
867  if(strtolower($player->getName()) === $partialName){
868  $matchedPlayers = [$player];
869  break;
870  }elseif(stripos($player->getName(), $partialName) !== false){
871  $matchedPlayers[] = $player;
872  }
873  }
874 
875  return $matchedPlayers;
876  }
877 
881  public function removePlayer(Player $player){
882  foreach($this->players as $identifier => $p){
883  if($player === $p){
884  unset($this->players[$identifier]);
885  break;
886  }
887  }
888  }
889 
893  public function getLevels(){
894  return $this->levels;
895  }
896 
900  public function getDefaultLevel(){
901  return $this->levelDefault;
902  }
903 
911  public function setDefaultLevel($level){
912  if($level === null or ($this->isLevelLoaded($level->getFolderName()) and $level !== $this->levelDefault)){
913  $this->levelDefault = $level;
914  }
915  }
916 
922  public function isLevelLoaded($name){
923  return $this->getLevelByName($name) instanceof Level;
924  }
925 
931  public function getLevel($levelId){
932  if(isset($this->levels[$levelId])){
933  return $this->levels[$levelId];
934  }
935 
936  return null;
937  }
938 
944  public function getLevelByName($name){
945  foreach($this->getLevels() as $level){
946  if($level->getFolderName() === $name){
947  return $level;
948  }
949  }
950 
951  return null;
952  }
953 
960  public function unloadLevel(Level $level, $forceUnload = false){
961  if($level->unload($forceUnload) === true){
962  unset($this->levels[$level->getId()]);
963 
964  return true;
965  }
966 
967  return false;
968  }
969 
979  public function loadLevel($name){
980  if(trim($name) === ""){
981  throw new LevelException("Invalid empty level name");
982  }
983  if($this->isLevelLoaded($name)){
984  return true;
985  }elseif(!$this->isLevelGenerated($name)){
986  $this->logger->notice("Level \"" . $name . "\" not found");
987 
988  return false;
989  }
990 
991  $path = $this->getDataPath() . "worlds/" . $name . "/";
992 
993  $provider = LevelProviderManager::getProvider($path);
994 
995  if($provider === null){
996  $this->logger->error("Could not load level \"" . $name . "\": Unknown provider");
997 
998  return false;
999  }
1000  //$entities = new Config($path."entities.yml", Config::YAML);
1001  //if(file_exists($path . "tileEntities.yml")){
1002  // @rename($path . "tileEntities.yml", $path . "tiles.yml");
1003  //}
1004 
1005  try{
1006  $level = new Level($this, $name, $path, $provider);
1007  }catch(\Exception $e){
1008  $this->logger->error("Could not load level \"" . $name . "\": " . $e->getMessage());
1009  if($this->logger instanceof MainLogger){
1010  $this->logger->logException($e);
1011  }
1012  return false;
1013  }
1014 
1015  $this->levels[$level->getId()] = $level;
1016 
1017  $level->initLevel();
1018 
1019  $this->getPluginManager()->callEvent(new LevelLoadEvent($level));
1020 
1021  /*foreach($entities->getAll() as $entity){
1022  if(!isset($entity["id"])){
1023  break;
1024  }
1025  if($entity["id"] === 64){ //Item Drop
1026  $e = $this->server->api->entity->add($this->levels[$name], ENTITY_ITEM, $entity["Item"]["id"], array(
1027  "meta" => $entity["Item"]["Damage"],
1028  "stack" => $entity["Item"]["Count"],
1029  "x" => $entity["Pos"][0],
1030  "y" => $entity["Pos"][1],
1031  "z" => $entity["Pos"][2],
1032  "yaw" => $entity["Rotation"][0],
1033  "pitch" => $entity["Rotation"][1],
1034  ));
1035  }elseif($entity["id"] === FALLING_SAND){
1036  $e = $this->server->api->entity->add($this->levels[$name], ENTITY_FALLING, $entity["id"], $entity);
1037  $e->setPosition(new Vector3($entity["Pos"][0], $entity["Pos"][1], $entity["Pos"][2]), $entity["Rotation"][0], $entity["Rotation"][1]);
1038  $e->setHealth($entity["Health"]);
1039  }elseif($entity["id"] === OBJECT_PAINTING or $entity["id"] === OBJECT_ARROW){ //Painting
1040  $e = $this->server->api->entity->add($this->levels[$name], ENTITY_OBJECT, $entity["id"], $entity);
1041  $e->setPosition(new Vector3($entity["Pos"][0], $entity["Pos"][1], $entity["Pos"][2]), $entity["Rotation"][0], $entity["Rotation"][1]);
1042  $e->setHealth(1);
1043  }else{
1044  $e = $this->server->api->entity->add($this->levels[$name], ENTITY_MOB, $entity["id"], $entity);
1045  $e->setPosition(new Vector3($entity["Pos"][0], $entity["Pos"][1], $entity["Pos"][2]), $entity["Rotation"][0], $entity["Rotation"][1]);
1046  $e->setHealth($entity["Health"]);
1047  }
1048  }*/
1049 
1050  /*if(file_exists($path . "tiles.yml")){
1051  $tiles = new Config($path . "tiles.yml", Config::YAML);
1052  foreach($tiles->getAll() as $tile){
1053  if(!isset($tile["id"])){
1054  continue;
1055  }
1056  $level->loadChunk($tile["x"] >> 4, $tile["z"] >> 4);
1057 
1058  $nbt = new Compound(false, []);
1059  foreach($tile as $index => $data){
1060  switch($index){
1061  case "Items":
1062  $tag = new Enum("Items", []);
1063  $tag->setTagType(NBT::TAG_Compound);
1064  foreach($data as $slot => $fields){
1065  $tag[(int) $slot] = new Compound(false, array(
1066  "Count" => new Byte("Count", $fields["Count"]),
1067  "Slot" => new Short("Slot", $fields["Slot"]),
1068  "Damage" => new Short("Damage", $fields["Damage"]),
1069  "id" => new String("id", $fields["id"])
1070  ));
1071  }
1072  $nbt["Items"] = $tag;
1073  break;
1074 
1075  case "id":
1076  case "Text1":
1077  case "Text2":
1078  case "Text3":
1079  case "Text4":
1080  $nbt[$index] = new String($index, $data);
1081  break;
1082 
1083  case "x":
1084  case "y":
1085  case "z":
1086  case "pairx":
1087  case "pairz":
1088  $nbt[$index] = new Int($index, $data);
1089  break;
1090 
1091  case "BurnTime":
1092  case "CookTime":
1093  case "MaxTime":
1094  $nbt[$index] = new Short($index, $data);
1095  break;
1096  }
1097  }
1098  switch($tile["id"]){
1099  case Tile::FURNACE:
1100  new Furnace($level, $nbt);
1101  break;
1102  case Tile::CHEST:
1103  new Chest($level, $nbt);
1104  break;
1105  case Tile::SIGN:
1106  new Sign($level, $nbt);
1107  break;
1108  }
1109  }
1110  unlink($path . "tiles.yml");
1111  $level->save(true, true);
1112  }*/
1113 
1114  return true;
1115  }
1116 
1127  public function generateLevel($name, $seed = null, $generator = null, $options = []){
1128  if(trim($name) === "" or $this->isLevelGenerated($name)){
1129  return false;
1130  }
1131 
1132  $seed = $seed === null ? Binary::readInt(@Utils::getRandomBytes(4, false)) : (int) $seed;
1133 
1134  if($generator !== null and class_exists($generator) and is_subclass_of($generator, Generator::class)){
1135  $generator = new $generator($options);
1136  }else{
1137  $options["preset"] = $this->getConfigString("generator-settings", "");
1138  $generator = Generator::getGenerator($this->getLevelType());
1139  }
1140 
1141  if(($provider = LevelProviderManager::getProviderByName($providerName = $this->getProperty("level-settings.default-format", "mcregion"))) === null){
1142  $provider = LevelProviderManager::getProviderByName($providerName = "mcregion");
1143  }
1144 
1145  $path = $this->getDataPath() . "worlds/" . $name . "/";
1147  $provider::generate($path, $name, $seed, $generator, $options);
1148 
1149  $level = new Level($this, $name, $path, $provider);
1150  $this->levels[$level->getId()] = $level;
1151 
1152  $level->initLevel();
1153 
1154  $this->getPluginManager()->callEvent(new LevelInitEvent($level));
1155 
1156  $this->getPluginManager()->callEvent(new LevelLoadEvent($level));
1157 
1158  $this->getLogger()->notice("Spawn terrain for level \"$name\" is being generated in the background");
1159 
1160 
1161  $radiusSquared = ($this->getViewDistance() + 1) / M_PI;
1162  $radius = ceil(sqrt($radiusSquared));
1163 
1164  $centerX = $level->getSpawnLocation()->getX() >> 4;
1165  $centerZ = $level->getSpawnLocation()->getZ() >> 4;
1166 
1167  $order = [];
1168 
1169  for($X = -$radius; $X <= $radius; ++$X){
1170  for($Z = -$radius; $Z <= $radius; ++$Z){
1171  $distance = $X ** 2 + $Z ** 2;
1172  if($distance > $radiusSquared){
1173  continue;
1174  }
1175  $chunkX = $X + $centerX;
1176  $chunkZ = $Z + $centerZ;
1177  $index = Level::chunkHash($chunkX, $chunkZ);
1178  $order[$index] = $distance;
1179  }
1180  }
1181 
1182  asort($order);
1183 
1184  $chunkX = $chunkZ = null;
1185 
1186  foreach($order as $index => $distance){
1187  Level::getXZ($index, $chunkX, $chunkZ);
1188  $level->generateChunk($chunkX, $chunkZ);
1189  }
1190 
1191  return true;
1192  }
1193 
1199  public function isLevelGenerated($name){
1200  if(trim($name) === ""){
1201  return false;
1202  }
1203  $path = $this->getDataPath() . "worlds/" . $name . "/";
1204  if(!($this->getLevelByName($name) instanceof Level)){
1205 
1206  if(LevelProviderManager::getProvider($path) === null){
1207  return false;
1208  }
1209  /*if(file_exists($path)){
1210  $level = new LevelImport($path);
1211  if($level->import() === false){ //Try importing a world
1212  return false;
1213  }
1214  }else{
1215  return false;
1216  }*/
1217  }
1218 
1219  return true;
1220  }
1221 
1228  public function getConfigString($variable, $defaultValue = ""){
1229  $v = getopt("", ["$variable::"]);
1230  if(isset($v[$variable])){
1231  return (string) $v[$variable];
1232  }
1233 
1234  return $this->properties->exists($variable) ? $this->properties->get($variable) : $defaultValue;
1235  }
1236 
1243  public function getProperty($variable, $defaultValue = null){
1244  $value = $this->config->getNested($variable);
1245 
1246  return $value === null ? $defaultValue : $value;
1247  }
1248 
1253  public function setConfigString($variable, $value){
1254  $this->properties->set($variable, $value);
1255  }
1256 
1263  public function getConfigInt($variable, $defaultValue = 0){
1264  $v = getopt("", ["$variable::"]);
1265  if(isset($v[$variable])){
1266  return (int) $v[$variable];
1267  }
1268 
1269  return $this->properties->exists($variable) ? (int) $this->properties->get($variable) : (int) $defaultValue;
1270  }
1271 
1276  public function setConfigInt($variable, $value){
1277  $this->properties->set($variable, (int) $value);
1278  }
1279 
1286  public function getConfigBoolean($variable, $defaultValue = false){
1287  $v = getopt("", ["$variable::"]);
1288  if(isset($v[$variable])){
1289  $value = $v[$variable];
1290  }else{
1291  $value = $this->properties->exists($variable) ? $this->properties->get($variable) : $defaultValue;
1292  }
1293 
1294  if(is_bool($value)){
1295  return $value;
1296  }
1297  switch(strtolower($value)){
1298  case "on":
1299  case "true":
1300  case "1":
1301  case "yes":
1302  return true;
1303  }
1304 
1305  return false;
1306  }
1307 
1312  public function setConfigBool($variable, $value){
1313  $this->properties->set($variable, $value == true ? "1" : "0");
1314  }
1315 
1321  public function getPluginCommand($name){
1322  if(($command = $this->commandMap->getCommand($name)) instanceof PluginIdentifiableCommand){
1323  return $command;
1324  }else{
1325  return null;
1326  }
1327  }
1328 
1332  public function getNameBans(){
1333  return $this->banByName;
1334  }
1335 
1339  public function getIPBans(){
1340  return $this->banByIP;
1341  }
1342 
1346  public function addOp($name){
1347  $this->operators->set(strtolower($name), true);
1348 
1349  if(($player = $this->getPlayerExact($name)) instanceof Player){
1350  $player->recalculatePermissions();
1351  }
1352  $this->operators->save();
1353  }
1354 
1358  public function removeOp($name){
1359  $this->operators->remove(strtolower($name));
1360 
1361  if(($player = $this->getPlayerExact($name)) instanceof Player){
1362  $player->recalculatePermissions();
1363  }
1364  $this->operators->save();
1365  }
1366 
1370  public function addWhitelist($name){
1371  $this->whitelist->set(strtolower($name), true);
1372  $this->whitelist->save();
1373  }
1374 
1378  public function removeWhitelist($name){
1379  $this->whitelist->remove(strtolower($name));
1380  $this->whitelist->save();
1381  }
1382 
1388  public function isWhitelisted($name){
1389  return !$this->hasWhitelist() or $this->operators->exists($name, true) or $this->whitelist->exists($name, true);
1390  }
1391 
1397  public function isOp($name){
1398  return $this->operators->exists($name, true);
1399  }
1400 
1404  public function getWhitelisted(){
1405  return $this->whitelist;
1406  }
1407 
1411  public function getOps(){
1412  return $this->operators;
1413  }
1414 
1415  public function reloadWhitelist(){
1416  $this->whitelist->reload();
1417  }
1418 
1422  public function getCommandAliases(){
1423  $section = $this->getProperty("aliases");
1424  $result = [];
1425  if(is_array($section)){
1426  foreach($section as $key => $value){
1427  $commands = [];
1428  if(is_array($value)){
1429  $commands = $value;
1430  }else{
1431  $commands[] = $value;
1432  }
1433 
1434  $result[$key] = $commands;
1435  }
1436  }
1437 
1438  return $result;
1439  }
1440 
1444  public static function getInstance(){
1445  return self::$instance;
1446  }
1447 
1455  public function __construct(\ClassLoader $autoloader, \ThreadedLogger $logger, $filePath, $dataPath, $pluginPath){
1456  self::$instance = $this;
1457 
1458  $this->autoloader = $autoloader;
1459  $this->logger = $logger;
1460  $this->filePath = $filePath;
1461  @mkdir($dataPath . "worlds/", 0777);
1462  @mkdir($dataPath . "players/", 0777);
1463  @mkdir($pluginPath, 0777);
1464 
1465  $this->dataPath = realpath($dataPath) . DIRECTORY_SEPARATOR;
1466  $this->pluginPath = realpath($pluginPath) . DIRECTORY_SEPARATOR;
1467 
1468  $this->entityMetadata = new EntityMetadataStore();
1469  $this->playerMetadata = new PlayerMetadataStore();
1470  $this->levelMetadata = new LevelMetadataStore();
1471 
1472  $this->operators = new Config($this->dataPath . "ops.txt", Config::ENUM);
1473  $this->whitelist = new Config($this->dataPath . "white-list.txt", Config::ENUM);
1474  if(file_exists($this->dataPath . "banned.txt") and !file_exists($this->dataPath . "banned-players.txt")){
1475  @rename($this->dataPath . "banned.txt", $this->dataPath . "banned-players.txt");
1476  }
1477  @touch($this->dataPath . "banned-players.txt");
1478  $this->banByName = new BanList($this->dataPath . "banned-players.txt");
1479  $this->banByName->load();
1480  @touch($this->dataPath . "banned-ips.txt");
1481  $this->banByIP = new BanList($this->dataPath . "banned-ips.txt");
1482  $this->banByIP->load();
1483 
1484  $this->consoleThreaded = new \Threaded();
1485  $this->console = new CommandReader($this->consoleThreaded);
1486 
1487  $version = new VersionString($this->getPocketMineVersion());
1488  $this->logger->info("Starting Minecraft: PE server version " . TextFormat::AQUA . $this->getVersion());
1489 
1490  $this->logger->info("Loading pocketmine.yml...");
1491  if(!file_exists($this->dataPath . "pocketmine.yml")){
1492  $content = file_get_contents($this->filePath . "src/pocketmine/resources/pocketmine.yml");
1493  if($version->isDev()){
1494  $content = str_replace("preferred-channel: stable", "preferred-channel: beta", $content);
1495  }
1496  @file_put_contents($this->dataPath . "pocketmine.yml", $content);
1497  }
1498  $this->config = new Config($this->dataPath . "pocketmine.yml", Config::YAML, []);
1499 
1500  $this->logger->info("Loading server properties...");
1501  $this->properties = new Config($this->dataPath . "server.properties", Config::PROPERTIES, [
1502  "motd" => "Minecraft: PE Server",
1503  "server-port" => 19132,
1504  "memory-limit" => "256M",
1505  "white-list" => false,
1506  "announce-player-achievements" => true,
1507  "spawn-protection" => 16,
1508  "max-players" => 20,
1509  "allow-flight" => false,
1510  "spawn-animals" => true,
1511  "spawn-mobs" => true,
1512  "gamemode" => 0,
1513  "force-gamemode" => false,
1514  "hardcore" => false,
1515  "pvp" => true,
1516  "difficulty" => 1,
1517  "generator-settings" => "",
1518  "level-name" => "world",
1519  "level-seed" => "",
1520  "level-type" => "DEFAULT",
1521  "enable-query" => true,
1522  "enable-rcon" => false,
1523  "rcon.password" => substr(base64_encode(@Utils::getRandomBytes(20, false)), 3, 10),
1524  "auto-save" => true,
1525  ]);
1526 
1527  ServerScheduler::$WORKERS = $this->getProperty("settings.async-workers", ServerScheduler::$WORKERS);
1528 
1529  $this->scheduler = new ServerScheduler();
1530 
1531  if($this->getConfigBoolean("enable-rcon", false) === true){
1532  $this->rcon = new RCON($this, $this->getConfigString("rcon.password", ""), $this->getConfigInt("rcon.port", $this->getPort()), ($ip = $this->getIp()) != "" ? $ip : "0.0.0.0", $this->getConfigInt("rcon.threads", 1), $this->getConfigInt("rcon.clients-per-thread", 50));
1533  }
1534 
1535  $this->maxPlayers = $this->getConfigInt("max-players", 20);
1536  $this->setAutoSave($this->getConfigBoolean("auto-save", true));
1537 
1538  if(($memory = str_replace("B", "", strtoupper($this->getConfigString("memory-limit", "256M")))) !== false){
1539  $value = ["M" => 1, "G" => 1024];
1540  $real = ((int) substr($memory, 0, -1)) * $value[substr($memory, -1)];
1541  if($real < 128){
1542  $this->logger->warning($this->getName() . " may not work right with less than 128MB of RAM", true, true, 0);
1543  }
1544  @ini_set("memory_limit", $memory);
1545  }else{
1546  $this->setConfigString("memory-limit", "256M");
1547  }
1548 
1549  if($this->getConfigBoolean("hardcore", false) === true and $this->getDifficulty() < 3){
1550  $this->setConfigInt("difficulty", 3);
1551  }
1552 
1553  define("pocketmine\\DEBUG", (int) $this->getProperty("debug.level", 1));
1554  if($this->logger instanceof MainLogger){
1555  $this->logger->setLogDebug(\pocketmine\DEBUG > 1);
1556  }
1557  define("ADVANCED_CACHE", $this->getProperty("settings.advanced-cache", false));
1558  if(ADVANCED_CACHE == true){
1559  $this->logger->info("Advanced cache enabled");
1560  }
1561 
1562  Level::$COMPRESSION_LEVEL = $this->getProperty("chunk-sending.compression-level", 8);
1563 
1564  if(defined("pocketmine\\DEBUG") and \pocketmine\DEBUG >= 0){
1565  @cli_set_process_title($this->getName() . " " . $this->getPocketMineVersion());
1566  }
1567 
1568  $this->logger->info("Starting Minecraft PE server on " . ($this->getIp() === "" ? "*" : $this->getIp()) . ":" . $this->getPort());
1569  define("BOOTUP_RANDOM", @Utils::getRandomBytes(16));
1570  $this->serverID = Binary::readLong(substr(Utils::getUniqueID(true, $this->getIp() . $this->getPort()), 0, 8));
1571 
1572  $this->addInterface($this->mainInterface = new RakLibInterface($this));
1573 
1574  $this->logger->info("This server is running " . $this->getName() . " version " . ($version->isDev() ? TextFormat::YELLOW : "") . $version->get(true) . TextFormat::WHITE . " \"" . $this->getCodename() . "\" (API " . $this->getApiVersion() . ")");
1575  $this->logger->info($this->getName() . " is distributed under the LGPL License");
1576 
1577  PluginManager::$pluginParentTimer = new TimingsHandler("** Plugins");
1578  Timings::init();
1579 
1580  $this->consoleSender = new ConsoleCommandSender();
1581  $this->commandMap = new SimpleCommandMap($this);
1582 
1583  $this->registerEntities();
1584  $this->registerTiles();
1585 
1586  InventoryType::init();
1587  Block::init();
1588  Item::init();
1589  TextWrapper::init();
1590  $this->craftingManager = new CraftingManager();
1591 
1592  $this->pluginManager = new PluginManager($this, $this->commandMap);
1593  $this->pluginManager->subscribeToPermission(Server::BROADCAST_CHANNEL_ADMINISTRATIVE, $this->consoleSender);
1594  $this->pluginManager->setUseTimings($this->getProperty("settings.enable-profiling", false));
1595  $this->pluginManager->registerInterface(PharPluginLoader::class);
1596 
1597  set_exception_handler([$this, "exceptionHandler"]);
1598  register_shutdown_function([$this, "crashDump"]);
1599 
1600  $this->pluginManager->loadPlugins($this->pluginPath);
1601 
1602  $this->updater = new AutoUpdater($this, $this->getProperty("auto-updater.host", "www.pocketmine.net"));
1603 
1604  $this->enablePlugins(PluginLoadOrder::STARTUP);
1605 
1606  if($this->getProperty("chunk-generation.use-async", true)){
1607  $this->generationManager = new GenerationRequestManager($this);
1608  }else{
1609  $this->generationManager = new GenerationInstanceManager($this);
1610  }
1611 
1612  LevelProviderManager::addProvider($this, Anvil::class);
1613  LevelProviderManager::addProvider($this, McRegion::class);
1614  if(extension_loaded("leveldb")){
1615  $this->logger->debug("Enabling LevelDB support");
1616  LevelProviderManager::addProvider($this, LevelDB::class);
1617  }
1618 
1619 
1620  Generator::addGenerator(Flat::class, "flat");
1621  Generator::addGenerator(Normal::class, "normal");
1622  Generator::addGenerator(Normal::class, "default");
1623 
1624  foreach((array) $this->getProperty("worlds", []) as $name => $worldSetting){
1625  if($this->loadLevel($name) === false){
1626  $seed = $this->getProperty("worlds.$name.seed", time());
1627  $options = explode(":", $this->getProperty("worlds.$name.generator", Generator::getGenerator("default")));
1628  $generator = Generator::getGenerator(array_shift($options));
1629  if(count($options) > 0){
1630  $options = [
1631  "preset" => implode(":", $options),
1632  ];
1633  }else{
1634  $options = [];
1635  }
1636 
1637  $this->generateLevel($name, $seed, $generator, $options);
1638  }
1639  }
1640 
1641  if($this->getDefaultLevel() === null){
1642  $default = $this->getConfigString("level-name", "world");
1643  if(trim($default) == ""){
1644  $this->getLogger()->warning("level-name cannot be null, using default");
1645  $default = "world";
1646  $this->setConfigString("level-name", "world");
1647  }
1648  if($this->loadLevel($default) === false){
1649  $seed = $this->getConfigInt("level-seed", time());
1650  $this->generateLevel($default, $seed === 0 ? time() : $seed);
1651  }
1652 
1653  $this->setDefaultLevel($this->getLevelByName($default));
1654  }
1655 
1656 
1657  $this->properties->save();
1658 
1659  if(!($this->getDefaultLevel() instanceof Level)){
1660  $this->getLogger()->emergency("No default level has been loaded");
1661  $this->forceShutdown();
1662 
1663  return;
1664  }
1665 
1666  $this->scheduler->scheduleDelayedRepeatingTask(new CallbackTask([Cache::class, "cleanup"]), $this->getProperty("ticks-per.cache-cleanup", 900), $this->getProperty("ticks-per.cache-cleanup", 900));
1667  if($this->getAutoSave() and $this->getProperty("ticks-per.autosave", 6000) > 0){
1668  $this->scheduler->scheduleDelayedRepeatingTask(new CallbackTask([$this, "doAutoSave"]), $this->getProperty("ticks-per.autosave", 6000), $this->getProperty("ticks-per.autosave", 6000));
1669  }
1670 
1671  if($this->getProperty("chunk-gc.period-in-ticks", 600) > 0){
1672  $this->scheduler->scheduleDelayedRepeatingTask(new CallbackTask([$this, "doLevelGC"]), $this->getProperty("chunk-gc.period-in-ticks", 600), $this->getProperty("chunk-gc.period-in-ticks", 600));
1673  }
1674 
1675  $this->enablePlugins(PluginLoadOrder::POSTWORLD);
1676 
1677  $this->start();
1678  }
1679 
1686  public function broadcastMessage($message, $recipients = null){
1687  if(!is_array($recipients)){
1688  return $this->broadcast($message, self::BROADCAST_CHANNEL_USERS);
1689  }
1690  foreach($recipients as $recipient){
1691  $recipient->sendMessage($message);
1692  }
1693  }
1694 
1701  public function broadcast($message, $permissions){
1703  $recipients = [];
1704  foreach(explode(";", $permissions) as $permission){
1705  foreach($this->pluginManager->getPermissionSubscriptions($permission) as $permissible){
1706  if($permissible instanceof CommandSender and $permissible->hasPermission($permission)){
1707  $recipients[spl_object_hash($permissible)] = $permissible; // do not send messages directly, or some might be repeated
1708  }
1709  }
1710  }
1711 
1712  foreach($recipients as $recipient){
1713  $recipient->sendMessage($message);
1714  }
1715 
1716  return count($recipients);
1717  }
1718 
1725  public static function broadcastPacket(array $players, DataPacket $packet){
1726  $packet->encode();
1727  $packet->isEncoded = true;
1728  foreach($players as $player){
1729  $player->dataPacket($packet);
1730  }
1731  if(isset($packet->__encapsulatedPacket)){
1732  unset($packet->__encapsulatedPacket);
1733  }
1734  }
1735 
1736 
1740  public function enablePlugins($type){
1741  foreach($this->pluginManager->getPlugins() as $plugin){
1742  if(!$plugin->isEnabled() and $plugin->getDescription()->getOrder() === $type){
1743  $this->enablePlugin($plugin);
1744  }
1745  }
1746 
1747  if($type === PluginLoadOrder::POSTWORLD){
1748  $this->commandMap->registerServerAliases();
1749  DefaultPermissions::registerCorePermissions();
1750  }
1751  }
1752 
1756  public function enablePlugin(Plugin $plugin){
1757  $this->pluginManager->enablePlugin($plugin);
1758  }
1759 
1765  public function loadPlugin(Plugin $plugin){
1766  $this->enablePlugin($plugin);
1767  }
1768 
1769  public function disablePlugins(){
1770  $this->pluginManager->disablePlugins();
1771  }
1772 
1773  public function checkConsole(){
1774  Timings::$serverCommandTimer->startTiming();
1775  if(($line = $this->console->getLine()) !== null){
1776  $this->pluginManager->callEvent($ev = new ServerCommandEvent($this->consoleSender, $line));
1777  if(!$ev->isCancelled()){
1778  $this->dispatchCommand($ev->getSender(), $ev->getCommand());
1779  }
1780  }
1781  Timings::$serverCommandTimer->stopTiming();
1782  }
1783 
1794  public function dispatchCommand(CommandSender $sender, $commandLine){
1795  if(!($sender instanceof CommandSender)){
1796  throw new ServerException("CommandSender is not valid");
1797  }
1798 
1799  if($this->commandMap->dispatch($sender, $commandLine)){
1800  return true;
1801  }
1802 
1803  if($sender instanceof Player){
1804  $sender->sendMessage("Unknown command. Type \"/help\" for help.");
1805  }else{
1806  $sender->sendMessage("Unknown command. Type \"help\" for help.");
1807  }
1808 
1809  return false;
1810  }
1811 
1812  public function reload(){
1813  $this->logger->info("Saving levels...");
1814 
1815  foreach($this->levels as $level){
1816  $level->save();
1817  }
1818 
1819  $this->pluginManager->disablePlugins();
1820  $this->pluginManager->clearPlugins();
1821  $this->commandMap->clearCommands();
1822 
1823  $this->logger->info("Reloading properties...");
1824  $this->properties->reload();
1825  $this->maxPlayers = $this->getConfigInt("max-players", 20);
1826 
1827  if(($memory = str_replace("B", "", strtoupper($this->getConfigString("memory-limit", "256M")))) !== false){
1828  $value = ["M" => 1, "G" => 1024];
1829  $real = ((int) substr($memory, 0, -1)) * $value[substr($memory, -1)];
1830  if($real < 256){
1831  $this->logger->warning($this->getName() . " may not work right with less than 256MB of RAM", true, true, 0);
1832  }
1833  @ini_set("memory_limit", $memory);
1834  }else{
1835  $this->setConfigString("memory-limit", "256M");
1836  }
1837 
1838  if($this->getConfigBoolean("hardcore", false) === true and $this->getDifficulty() < 3){
1839  $this->setConfigInt("difficulty", 3);
1840  }
1841 
1842  $this->banByIP->load();
1843  $this->banByName->load();
1844  $this->reloadWhitelist();
1845  $this->operators->reload();
1846 
1847  foreach($this->getIPBans()->getEntries() as $entry){
1848  $this->blockAddress($entry->getName(), -1);
1849  }
1850 
1851  $this->pluginManager->registerInterface(PharPluginLoader::class);
1852  $this->pluginManager->loadPlugins($this->pluginPath);
1853  $this->enablePlugins(PluginLoadOrder::STARTUP);
1854  $this->enablePlugins(PluginLoadOrder::POSTWORLD);
1855  TimingsHandler::reload();
1856  }
1857 
1861  public function shutdown(){
1862  $this->isRunning = false;
1863  gc_collect_cycles();
1864  }
1865 
1866  public function forceShutdown(){
1867  if($this->hasStopped){
1868  return;
1869  }
1870 
1871  try{
1872  $this->hasStopped = true;
1873 
1874  $this->shutdown();
1875  if($this->rcon instanceof RCON){
1876  $this->rcon->stop();
1877  }
1878 
1879  if($this->getProperty("settings.upnp-forwarding", false) === true){
1880  $this->logger->info("[UPnP] Removing port forward...");
1881  UPnP::RemovePortForward($this->getPort());
1882  }
1883 
1884  $this->pluginManager->disablePlugins();
1885 
1886  foreach($this->players as $player){
1887  $player->close(TextFormat::YELLOW . $player->getName() . " has left the game", $this->getProperty("settings.shutdown-message", "Server closed"));
1888  }
1889 
1890  foreach($this->getLevels() as $level){
1891  $this->unloadLevel($level, true);
1892  }
1893 
1894  if($this->generationManager instanceof GenerationRequestManager){
1895  $this->generationManager->shutdown();
1896  }
1897 
1898  HandlerList::unregisterAll();
1899 
1900  $this->scheduler->cancelAllTasks();
1901  $this->scheduler->mainThreadHeartbeat(PHP_INT_MAX);
1902 
1903  $this->properties->save();
1904 
1905  $this->console->kill();
1906 
1907  foreach($this->interfaces as $interface){
1908  $interface->shutdown();
1909  }
1910  }catch(\Exception $e){
1911  $this->logger->emergency("Crashed while crashing, killing process");
1912  @kill(getmypid());
1913  }
1914 
1915  }
1916 
1920  public function start(){
1921 
1922  if($this->getConfigBoolean("enable-query", true) === true){
1923  $this->queryHandler = new QueryHandler();
1924 
1925  }
1926 
1927  foreach($this->getIPBans()->getEntries() as $entry){
1928  $this->blockAddress($entry->getName(), -1);
1929  }
1930 
1931  if($this->getProperty("settings.send-usage", true) !== false){
1932  $this->scheduler->scheduleDelayedRepeatingTask(new CallbackTask([$this, "sendUsage"]), 6000, 6000);
1933  $this->sendUsage();
1934  }
1935 
1936 
1937  if($this->getProperty("settings.upnp-forwarding", false) == true){
1938  $this->logger->info("[UPnP] Trying to port forward...");
1939  UPnP::PortForward($this->getPort());
1940  }
1941 
1942  $this->tickCounter = 0;
1943 
1944  if(function_exists("pcntl_signal")){
1945  pcntl_signal(SIGTERM, [$this, "handleSignal"]);
1946  pcntl_signal(SIGINT, [$this, "handleSignal"]);
1947  pcntl_signal(SIGHUP, [$this, "handleSignal"]);
1948  $this->getScheduler()->scheduleRepeatingTask(new CallbackTask("pcntl_signal_dispatch"), 5);
1949  }
1950 
1951 
1952  $this->getScheduler()->scheduleRepeatingTask(new CallbackTask([$this, "checkTicks"]), 20 * 5);
1953 
1954  $this->logger->info("Default game type: " . self::getGamemodeString($this->getGamemode()));
1955 
1956  $this->logger->info("Done (" . round(microtime(true) - \pocketmine\START_TIME, 3) . 's)! For help, type "help" or "?"');
1957 
1958  $this->tickProcessor();
1959  $this->forceShutdown();
1960 
1961  gc_collect_cycles();
1962  }
1963 
1964  public function handleSignal($signo){
1965  if($signo === SIGTERM or $signo === SIGINT or $signo === SIGHUP){
1966  $this->shutdown();
1967  }
1968  }
1969 
1970  public function checkTicks(){
1971  if($this->getTicksPerSecond() < 12){
1972  $this->logger->warning("Can't keep up! Is the server overloaded?");
1973  }
1974  }
1975 
1976  public function checkMemory(){
1977  //TODO
1978  $info = $this->debugInfo();
1979  $data = $info["memory_usage"] . "," . $info["players"] . "," . $info["entities"];
1980  $i = count($this->memoryStats) - 1;
1981  if($i < 0 or $this->memoryStats[$i] !== $data){
1982  $this->memoryStats[] = $data;
1983  }
1984  }
1985 
1986  public function exceptionHandler(\Exception $e, $trace = null){
1987  if($e === null){
1988  return;
1989  }
1990 
1991  global $lastError;
1992 
1993  if($trace === null){
1994  $trace = $e->getTrace();
1995  }
1996 
1997  $errstr = $e->getMessage();
1998  $errfile = $e->getFile();
1999  $errno = $e->getCode();
2000  $errline = $e->getLine();
2001 
2002  $type = ($errno === E_ERROR or $errno === E_USER_ERROR) ? \LogLevel::ERROR : (($errno === E_USER_WARNING or $errno === E_WARNING) ? \LogLevel::WARNING : \LogLevel::NOTICE);
2003  if(($pos = strpos($errstr, "\n")) !== false){
2004  $errstr = substr($errstr, 0, $pos);
2005  }
2006 
2007  $errfile = cleanPath($errfile);
2008 
2009  if($this->logger instanceof MainLogger){
2010  $this->logger->logException($e, $trace);
2011  }
2012 
2013  $lastError = [
2014  "type" => $type,
2015  "message" => $errstr,
2016  "fullFile" => $e->getFile(),
2017  "file" => $errfile,
2018  "line" => $errline,
2019  "trace" => @getTrace(1, $trace)
2020  ];
2021 
2022  global $lastExceptionError, $lastError;
2023  $lastExceptionError = $lastError;
2024  $this->crashDump();
2025  }
2026 
2027  public function crashDump(){
2028  if($this->isRunning === false){
2029  return;
2030  }
2031  $this->isRunning = false;
2032  $this->hasStopped = false;
2033 
2034  ini_set("error_reporting", 0);
2035  ini_set("memory_limit", -1); //Fix error dump not dumped on memory problems
2036  $this->logger->emergency("An unrecoverable error has occurred and the server has crashed. Creating a crash dump");
2037  $dump = new CrashDump($this);
2038 
2039  $this->logger->emergency("Please submit the \"" . $dump->getPath() . "\" file to the Bug Reporting page. Give as much info as you can.");
2040 
2041 
2042  if($this->getProperty("auto-report.enabled", true) !== false){
2043  $report = true;
2044  $plugin = $dump->getData()["plugin"];
2045  if(is_string($plugin)){
2046  $p = $this->pluginManager->getPlugin($plugin);
2047  if($p instanceof Plugin and !($p->getPluginLoader() instanceof PharPluginLoader)){
2048  $report = false;
2049  }
2050  }elseif(\Phar::running(true) == ""){
2051  $report = false;
2052  }
2053  if($dump->getData()["error"]["type"] === "E_PARSE" or $dump->getData()["error"]["type"] === "E_COMPILE_ERROR"){
2054  $report = false;
2055  }
2056 
2057  if($report){
2058  $reply = Utils::postURL("http://" . $this->getProperty("auto-report.host", "crash.pocketmine.net") . "/submit/api", [
2059  "report" => "yes",
2060  "name" => $this->getName() . " " . $this->getPocketMineVersion(),
2061  "email" => "crash@pocketmine.net",
2062  "reportPaste" => base64_encode($dump->getEncodedData())
2063  ]);
2064 
2065  if(($data = json_decode($reply)) !== false and isset($data->crashId)){
2066  $reportId = $data->crashId;
2067  $reportUrl = $data->crashUrl;
2068  $this->logger->emergency("The crash dump has been automatically submitted to the Crash Archive. You can view it on $reportUrl or use the ID #$reportId.");
2069  }
2070  }
2071  }
2072 
2073  //$this->checkMemory();
2074  //$dump .= "Memory Usage Tracking: \r\n" . chunk_split(base64_encode(gzdeflate(implode(";", $this->memoryStats), 9))) . "\r\n";
2075 
2076  $this->forceShutdown();
2077  @kill(getmypid());
2078  exit(1);
2079  }
2080 
2081  public function __debugInfo(){
2082  return [];
2083  }
2084 
2085  private function tickProcessor(){
2086  while($this->isRunning){
2087  $this->tick();
2088  usleep((int) max(1, ($this->nextTick - microtime(true)) * 1000000));
2089  }
2090  }
2091 
2092  public function addPlayer($identifier, Player $player){
2093  $this->players[$identifier] = $player;
2094  }
2095 
2096  private function checkTickUpdates($currentTick){
2097 
2098  //Do level ticks
2099  foreach($this->getLevels() as $level){
2100  try{
2101  $level->doTick($currentTick);
2102  }catch(\Exception $e){
2103  $this->logger->critical("Could not tick level " . $level->getName() . ": " . $e->getMessage());
2104  if(\pocketmine\DEBUG > 1 and $this->logger instanceof MainLogger){
2105  $this->logger->logException($e);
2106  }
2107  }
2108  }
2109  }
2110 
2111  public function doAutoSave(){
2112  if($this->getAutoSave()){
2113  Timings::$worldSaveTimer->startTiming();
2114  foreach($this->getOnlinePlayers() as $index => $player){
2115  if($player->isOnline()){
2116  $player->save();
2117  }elseif(!$player->isConnected()){
2118  unset($this->players[$index]);
2119  }
2120  }
2121 
2122  foreach($this->getLevels() as $level){
2123  $level->save(false);
2124  }
2125  Timings::$worldSaveTimer->stopTiming();
2126  }
2127  }
2128 
2129  public function doLevelGC(){
2130  foreach($this->getLevels() as $level){
2131  $level->doChunkGarbageCollection();
2132  }
2133  }
2134 
2135  public function sendUsage(){
2136  if($this->lastSendUsage instanceof SendUsageTask){
2137  if(!$this->lastSendUsage->isFinished()){ //do not call multiple times
2138  return;
2139  }
2140  }
2141 
2142  $plist = "";
2143  foreach($this->getPluginManager()->getPlugins() as $p){
2144  $d = $p->getDescription();
2145  $plist .= str_replace([";", ":"], "", $d->getName()) . ":" . str_replace([";", ":"], "", $d->getVersion()) . ";";
2146  }
2147 
2148  $version = new VersionString();
2149  $this->lastSendUsage = new SendUsageTask("http://stats.pocketmine.net/usage.php", [
2150  "serverid" => $this->serverID,
2151  "port" => $this->getPort(),
2152  "os" => Utils::getOS(),
2153  "name" => $this->getName(),
2154  "memory_total" => $this->getConfigString("memory-limit"),
2155  "memory_usage" => memory_get_usage(),
2156  "php_version" => PHP_VERSION,
2157  "version" => $version->get(true),
2158  "build" => $version->getBuild(),
2159  "mc_version" => \pocketmine\MINECRAFT_VERSION,
2160  "protocol" => network\protocol\Info::CURRENT_PROTOCOL,
2161  "online" => count($this->players),
2162  "max" => $this->getMaxPlayers(),
2163  "plugins" => $plist,
2164  ]);
2165 
2166  $this->scheduler->scheduleAsyncTask($this->lastSendUsage);
2167  }
2168 
2169  private function titleTick(){
2170  if(defined("pocketmine\\DEBUG") and \pocketmine\DEBUG >= 0 and \pocketmine\ANSI === true){
2171  echo "\x1b]0;" . $this->getName() . " " . $this->getPocketMineVersion() . " | Online " . count($this->players) . "/" . $this->getMaxPlayers() . " | RAM " . round((memory_get_usage() / 1024) / 1024, 2) . "/" . round((memory_get_usage(true) / 1024) / 1024, 2) . " MB | U " . round($this->mainInterface->getUploadUsage() / 1024, 2) . " D " . round($this->mainInterface->getDownloadUsage() / 1024, 2) . " kB/s | TPS " . $this->getTicksPerSecond() . " | Load " . $this->getTickUsage() . "%\x07";
2172  }
2173  }
2174 
2175 
2179  private function tick(){
2180  $tickTime = microtime(true);
2181  if($tickTime < $this->nextTick){
2182  return false;
2183  }
2184 
2185  Timings::$serverTickTimer->startTiming();
2186 
2187  ++$this->tickCounter;
2188 
2189  $this->checkConsole();
2190 
2191  Timings::$connectionTimer->startTiming();
2192  foreach($this->interfaces as $interface){
2193  $interface->process();
2194  }
2195  Timings::$connectionTimer->stopTiming();
2196 
2197  Timings::$schedulerTimer->startTiming();
2198  $this->scheduler->mainThreadHeartbeat($this->tickCounter);
2199  Timings::$schedulerTimer->stopTiming();
2200 
2201  $this->checkTickUpdates($this->tickCounter);
2202 
2203  if(($this->tickCounter & 0b1111) === 0){
2204  $this->titleTick();
2205  if(isset($this->queryHandler) and ($this->tickCounter & 0b111111111) === 0){
2206  try{
2207  $this->queryHandler->regenerateInfo();
2208  }catch(\Exception $e){
2209  if($this->logger instanceof MainLogger){
2210  $this->logger->logException($e);
2211  }
2212  }
2213  }
2214  }
2215 
2216  Timings::$generationTimer->startTiming();
2217  try{
2218  $this->generationManager->process();
2219  }catch(\Exception $e){
2220  if($this->logger instanceof MainLogger){
2221  $this->logger->logException($e);
2222  }
2223  }
2224  Timings::$generationTimer->stopTiming();
2225 
2226  if(($this->tickCounter % 100) === 0){
2227  foreach($this->levels as $level){
2228  $level->clearCache();
2229  }
2230  }
2231 
2232 
2233  Timings::$serverTickTimer->stopTiming();
2234 
2235  TimingsHandler::tick();
2236 
2237  $now = microtime(true);
2238  array_shift($this->tickAverage);
2239  $this->tickAverage[] = min(20, 1 / max(0.001, $now - $tickTime));
2240  array_shift($this->useAverage);
2241  $this->useAverage[] = min(1, ($now - $tickTime) / 0.05);
2242 
2243  if(($this->nextTick - $tickTime) < -1){
2244  $this->nextTick = $tickTime;
2245  }
2246  $this->nextTick += 0.05;
2247 
2248  return true;
2249  }
2250 
2251  private function registerEntities(){
2252  Entity::registerEntity(Arrow::class);
2253  Entity::registerEntity(DroppedItem::class);
2254  Entity::registerEntity(FallingSand::class);
2255  Entity::registerEntity(PrimedTNT::class);
2256  Entity::registerEntity(Snowball::class);
2257  Entity::registerEntity(Villager::class);
2258  Entity::registerEntity(Zombie::class);
2259 
2260  Entity::registerEntity(Human::class, true);
2261  }
2262 
2263  private function registerTiles(){
2264  Tile::registerTile(Chest::class);
2265  Tile::registerTile(Furnace::class);
2266  Tile::registerTile(Sign::class);
2267  }
2268 
2269 }
saveOfflinePlayerData($name, Compound $nbtTag)
Definition: Server.php:811
getPluginCommand($name)
Definition: Server.php:1321
setConfigBool($variable, $value)
Definition: Server.php:1312
matchPlayer($partialName)
Definition: Server.php:863
getConfigBoolean($variable, $defaultValue=false)
Definition: Server.php:1286
static getGamemodeFromString($str)
Definition: Server.php:394
getConfigString($variable, $defaultValue="")
Definition: Server.php:1228
setConfigInt($variable, $value)
Definition: Server.php:1276
static getDifficultyFromString($str)
Definition: Server.php:425
isLevelGenerated($name)
Definition: Server.php:1199
addInterface(SourceInterface $interface)
Definition: Server.php:604
removeWhitelist($name)
Definition: Server.php:1378
removeInterface(SourceInterface $interface)
Definition: Server.php:611
setAutoSave($value)
Definition: Server.php:330
unloadLevel(Level $level, $forceUnload=false)
Definition: Server.php:960
__construct(\ClassLoader $autoloader,\ThreadedLogger $logger, $filePath, $dataPath, $pluginPath)
Definition: Server.php:1455
static getInstance()
Definition: Server.php:1444
getPlayerExact($name)
Definition: Server.php:847
static broadcastPacket(array $players, DataPacket $packet)
Definition: Server.php:1725
getConfigInt($variable, $defaultValue=0)
Definition: Server.php:1263
unload($force=false)
Definition: Level.php:380
setDefaultLevel($level)
Definition: Server.php:911
isWhitelisted($name)
Definition: Server.php:1388
getLevel($levelId)
Definition: Server.php:931
handlePacket($address, $port, $payload)
Definition: Server.php:640
enablePlugins($type)
Definition: Server.php:1740
static getGamemodeString($mode)
Definition: Server.php:372
isLevelLoaded($name)
Definition: Server.php:922
loadLevel($name)
Definition: Server.php:979
getPlayer($name)
Definition: Server.php:822
sendPacket($address, $port, $payload)
Definition: Server.php:621
setConfigString($variable, $value)
Definition: Server.php:1253
blockAddress($address, $timeout=300)
Definition: Server.php:631
addWhitelist($name)
Definition: Server.php:1370
enablePlugin(Plugin $plugin)
Definition: Server.php:1756
getOfflinePlayerData($name)
Definition: Server.php:696
dispatchCommand(CommandSender $sender, $commandLine)
Definition: Server.php:1794
removePlayer(Player $player)
Definition: Server.php:881
getProperty($variable, $defaultValue=null)
Definition: Server.php:1243
broadcastMessage($message, $recipients=null)
Definition: Server.php:1686
getLevelByName($name)
Definition: Server.php:944
loadPlugin(Plugin $plugin)
Definition: Server.php:1765
getOfflinePlayer($name)
Definition: Server.php:680