187    public const BROADCAST_CHANNEL_ADMINISTRATIVE = 
"pocketmine.broadcast.admin";
 
  188    public const BROADCAST_CHANNEL_USERS = 
"pocketmine.broadcast.user";
 
  190    public const DEFAULT_SERVER_NAME = VersionInfo::NAME . 
" Server";
 
  191    public const DEFAULT_MAX_PLAYERS = 20;
 
  192    public const DEFAULT_PORT_IPV4 = 19132;
 
  193    public const DEFAULT_PORT_IPV6 = 19133;
 
  194    public const DEFAULT_MAX_VIEW_DISTANCE = 16;
 
  201    public const TARGET_TICKS_PER_SECOND = 20;
 
  205    public const TARGET_SECONDS_PER_TICK = 1 / self::TARGET_TICKS_PER_SECOND;
 
  206    public const TARGET_NANOSECONDS_PER_TICK = 1_000_000_000 / self::TARGET_TICKS_PER_SECOND;
 
  211    private const TPS_OVERLOAD_WARNING_THRESHOLD = self::TARGET_TICKS_PER_SECOND * 0.6;
 
  213    private const TICKS_PER_WORLD_CACHE_CLEAR = 5 * self::TARGET_TICKS_PER_SECOND;
 
  214    private const TICKS_PER_TPS_OVERLOAD_WARNING = 5 * self::TARGET_TICKS_PER_SECOND;
 
  215    private const TICKS_PER_STATS_REPORT = 300 * self::TARGET_TICKS_PER_SECOND;
 
  217    private const DEFAULT_ASYNC_COMPRESSION_THRESHOLD = 10_000;
 
  219    private static ?
Server $instance = 
null;
 
  227    private Config $operators;
 
  229    private Config $whitelist;
 
  231    private bool $isRunning = 
true;
 
  233    private bool $hasStopped = 
false;
 
  237    private float $profilingTickRate = self::TARGET_TICKS_PER_SECOND;
 
  244    private int $tickCounter = 0;
 
  245    private float $nextTick = 0;
 
  247    private array $tickAverage;
 
  249    private array $useAverage;
 
  250    private float $currentTPS = self::TARGET_TICKS_PER_SECOND;
 
  251    private float $currentUse = 0;
 
  252    private float $startTime;
 
  254    private bool $doTitleTick = 
true;
 
  256    private int $sendUsageTicker = 0;
 
  271    private int $maxPlayers;
 
  273    private bool $onlineMode = 
true;
 
  277    private bool $networkCompressionAsync = 
true;
 
  278    private int $networkCompressionAsyncThreshold = self::DEFAULT_ASYNC_COMPRESSION_THRESHOLD;
 
  281    private bool $forceLanguage = 
false;
 
  283    private UuidInterface $serverID;
 
  285    private string $dataPath;
 
  286    private string $pluginPath;
 
  294    private array $uniquePlayers = [];
 
  301    private array $playerList = [];
 
  309    private array $broadcastSubscribers = [];
 
  311    public function getName() : string{
 
  315    public function isRunning() : bool{
 
  316        return $this->isRunning;
 
  319    public function getPocketMineVersion() : string{
 
  320        return VersionInfo::VERSION()->getFullVersion(true);
 
  323    public function getVersion() : string{
 
  324        return ProtocolInfo::MINECRAFT_VERSION;
 
  327    public function getApiVersion() : string{
 
  328        return VersionInfo::BASE_VERSION;
 
  331    public function getFilePath() : string{
 
  335    public function getResourcePath() : string{
 
  339    public function getDataPath() : string{
 
  340        return $this->dataPath;
 
  343    public function getPluginPath() : string{
 
  344        return $this->pluginPath;
 
  347    public function getMaxPlayers() : int{
 
  348        return $this->maxPlayers;
 
  356        return $this->onlineMode;
 
 
  363        return $this->getOnlineMode();
 
 
  366    public function getPort() : int{
 
  367        return $this->configGroup->getConfigInt(
ServerProperties::SERVER_PORT_IPV4, self::DEFAULT_PORT_IPV4);
 
  370    public function getPortV6() : int{
 
  371        return $this->configGroup->getConfigInt(ServerProperties::SERVER_PORT_IPV6, self::DEFAULT_PORT_IPV6);
 
  374    public function getViewDistance() : int{
 
  375        return max(2, $this->configGroup->getConfigInt(ServerProperties::VIEW_DISTANCE, self::DEFAULT_MAX_VIEW_DISTANCE));
 
  382        return max(2, min($distance, $this->memoryManager->getViewDistance($this->getViewDistance())));
 
 
  385    public function getIp() : string{
 
  387        return $str !== 
"" ? $str : 
"0.0.0.0";
 
  390    public function getIpV6() : string{
 
  391        $str = $this->configGroup->getConfigString(ServerProperties::SERVER_IPV6);
 
  392        return $str !== 
"" ? $str : 
"::";
 
  395    public function getServerUniqueId() : UuidInterface{
 
  396        return $this->serverID;
 
  399    public function getGamemode() : GameMode{
 
  400        return GameMode::fromString($this->configGroup->getConfigString(ServerProperties::GAME_MODE)) ?? GameMode::SURVIVAL;
 
  403    public function getForceGamemode() : bool{
 
  404        return $this->configGroup->getConfigBool(ServerProperties::FORCE_GAME_MODE, false);
 
  414    public function hasWhitelist() : bool{
 
  415        return $this->configGroup->getConfigBool(
ServerProperties::WHITELIST, false);
 
  418    public function isHardcore() : bool{
 
  419        return $this->configGroup->getConfigBool(ServerProperties::HARDCORE, false);
 
  422    public function getMotd() : string{
 
  423        return $this->configGroup->getConfigString(ServerProperties::MOTD, self::DEFAULT_SERVER_NAME);
 
  426    public function getLoader() : ThreadSafeClassLoader{
 
  427        return $this->autoloader;
 
  430    public function getLogger() : AttachableThreadSafeLogger{
 
  431        return $this->logger;
 
  434    public function getUpdater() : UpdateChecker{
 
  435        return $this->updater;
 
  438    public function getPluginManager() : PluginManager{
 
  439        return $this->pluginManager;
 
  442    public function getCraftingManager() : CraftingManager{
 
  443        return $this->craftingManager;
 
  446    public function getResourcePackManager() : ResourcePackManager{
 
  447        return $this->resourceManager;
 
  450    public function getWorldManager() : WorldManager{
 
  451        return $this->worldManager;
 
  454    public function getAsyncPool() : AsyncPool{
 
  455        return $this->asyncPool;
 
  458    public function getTick() : int{
 
  459        return $this->tickCounter;
 
  466        return round($this->currentTPS, 2);
 
 
  473        return round(array_sum($this->tickAverage) / count($this->tickAverage), 2);
 
 
  480        return round($this->currentUse * 100, 2);
 
 
  487        return round((array_sum($this->useAverage) / count($this->useAverage)) * 100, 2);
 
 
  490    public function getStartTime() : float{
 
  491        return $this->startTime;
 
  494    public function getCommandMap() : SimpleCommandMap{
 
  495        return $this->commandMap;
 
  502        return $this->playerList;
 
 
  505    public function shouldSavePlayerData() : bool{
 
  506        return $this->configGroup->getPropertyBool(Yml::PLAYER_SAVE_PLAYER_DATA, true);
 
  509    public function getOfflinePlayer(
string $name) : Player|OfflinePlayer|null{
 
  510        $name = strtolower($name);
 
  511        $result = $this->getPlayerExact($name);
 
  513        if($result === 
null){
 
  514            $result = 
new OfflinePlayer($name, $this->getOfflinePlayerData($name));
 
  524        return $this->playerDataProvider->hasData($name);
 
 
  527    public function getOfflinePlayerData(
string $name) : ?
CompoundTag{
 
  530                return $this->playerDataProvider->loadData($name);
 
  531            }
catch(PlayerDataLoadException $e){
 
  532                $this->logger->debug(
"Failed to load player data for $name: " . $e->getMessage());
 
  533                $this->logger->error($this->language->translate(KnownTranslationFactory::pocketmine_data_playerCorrupted($name)));
 
  539    public function saveOfflinePlayerData(
string $name, CompoundTag $nbtTag) : void{
 
  540        $ev = new PlayerDataSaveEvent($nbtTag, $name, $this->getPlayerExact($name));
 
  541        if(!$this->shouldSavePlayerData()){
 
  547        if(!$ev->isCancelled()){
 
  548            Timings::$syncPlayerDataSave->time(function() use ($name, $ev) : void{
 
  550                    $this->playerDataProvider->saveData($name, $ev->getSaveData());
 
  551                }catch(PlayerDataSaveException $e){
 
  552                    $this->logger->critical($this->language->translate(KnownTranslationFactory::pocketmine_data_saveError($name, $e->getMessage())));
 
  553                    $this->logger->logException($e);
 
  565        $class = $ev->getPlayerClass();
 
  567        if($offlinePlayerData !== 
null && ($world = $this->worldManager->getWorldByName($offlinePlayerData->getString(Player::TAG_LEVEL, 
""))) !== 
null){
 
  568            $playerPos = EntityDataHelper::parseLocation($offlinePlayerData, $world);
 
  570            $world = $this->worldManager->getDefaultWorld();
 
  572                throw new AssumptionFailedError(
"Default world should always be loaded");
 
  579        $createPlayer = 
function(
Location $location) use ($playerPromiseResolver, $class, $session, $playerInfo, $authenticated, $offlinePlayerData) : 
void{
 
  581            $player = 
new $class($this, $session, $playerInfo, $authenticated, $location, $offlinePlayerData);
 
  582            if(!$player->hasPlayedBefore()){
 
  583                $player->onGround = 
true; 
 
  585            $playerPromiseResolver->resolve($player);
 
  588        if($playerPos === 
null){ 
 
  589            $world->requestSafeSpawn()->onCompletion(
 
  590                function(Position $spawn) use ($createPlayer, $playerPromiseResolver, $session, $world) : 
void{
 
  591                    if(!$session->isConnected()){
 
  592                        $playerPromiseResolver->reject();
 
  595                    $createPlayer(Location::fromObject($spawn, $world));
 
  597                function() use ($playerPromiseResolver, $session) : void{
 
  598                    if($session->isConnected()){
 
  599                        $session->disconnectWithError(KnownTranslationFactory::pocketmine_disconnect_error_respawn());
 
  601                    $playerPromiseResolver->reject();
 
  605            $createPlayer($playerPos);
 
  608        return $playerPromiseResolver->getPromise();
 
 
  623        $name = strtolower($name);
 
  624        $delta = PHP_INT_MAX;
 
  625        foreach($this->getOnlinePlayers() as $player){
 
  626            if(stripos($player->getName(), $name) === 0){
 
  627                $curDelta = strlen($player->getName()) - strlen($name);
 
  628                if($curDelta < $delta){
 
 
  645        $name = strtolower($name);
 
  646        foreach($this->getOnlinePlayers() as $player){
 
  647            if(strtolower($player->getName()) === $name){
 
 
  659        return $this->playerList[$rawUUID] ?? null;
 
 
  666        return $this->getPlayerByRawUUID($uuid->getBytes());
 
 
  670        return $this->configGroup;
 
  678        if(($command = $this->commandMap->getCommand($name)) instanceof 
PluginOwned){
 
 
  685    public function getNameBans() : 
BanList{
 
  686        return $this->banByName;
 
  689    public function getIPBans() : BanList{
 
  690        return $this->banByIP;
 
  693    public function addOp(
string $name) : void{
 
  694        $this->operators->set(strtolower($name), true);
 
  696        if(($player = $this->getPlayerExact($name)) !== 
null){
 
  697            $player->setBasePermission(DefaultPermissions::ROOT_OPERATOR, 
true);
 
  699        $this->operators->save();
 
  702    public function removeOp(
string $name) : void{
 
  703        $lowercaseName = strtolower($name);
 
  704        foreach(Utils::promoteKeys($this->operators->getAll()) as $operatorName => $_){
 
  705            $operatorName = (string) $operatorName;
 
  706            if($lowercaseName === strtolower($operatorName)){
 
  707                $this->operators->remove($operatorName);
 
  711        if(($player = $this->getPlayerExact($name)) !== 
null){
 
  712            $player->unsetBasePermission(DefaultPermissions::ROOT_OPERATOR);
 
  714        $this->operators->save();
 
  717    public function addWhitelist(
string $name) : void{
 
  718        $this->whitelist->set(strtolower($name), true);
 
  719        $this->whitelist->save();
 
  722    public function removeWhitelist(
string $name) : void{
 
  723        $this->whitelist->remove(strtolower($name));
 
  724        $this->whitelist->save();
 
  727    public function isWhitelisted(
string $name) : bool{
 
  728        return !$this->hasWhitelist() || $this->operators->exists($name, true) || $this->whitelist->exists($name, true);
 
  731    public function isOp(
string $name) : bool{
 
  732        return $this->operators->exists($name, true);
 
  735    public function getWhitelisted() : Config{
 
  736        return $this->whitelist;
 
  739    public function getOps() : Config{
 
  740        return $this->operators;
 
  748        $section = $this->configGroup->getProperty(Yml::ALIASES);
 
  750        if(is_array($section)){
 
  751            foreach(Utils::promoteKeys($section) as $key => $value){
 
  755                if(is_array($value)){
 
  758                    $commands[] = (string) $value;
 
  761                $result[(string) $key] = $commands;
 
 
  768    public static function getInstance() : Server{
 
  769        if(self::$instance === null){
 
  770            throw new \RuntimeException(
"Attempt to retrieve Server instance outside server thread");
 
  772        return self::$instance;
 
  775    public function __construct(
 
  776        private ThreadSafeClassLoader $autoloader,
 
  777        private AttachableThreadSafeLogger $logger,
 
  781        if(self::$instance !== 
null){
 
  782            throw new \LogicException(
"Only one server instance can exist at once");
 
  784        self::$instance = $this;
 
  785        $this->startTime = microtime(
true);
 
  786        $this->tickAverage = array_fill(0, self::TARGET_TICKS_PER_SECOND, self::TARGET_TICKS_PER_SECOND);
 
  787        $this->useAverage = array_fill(0, self::TARGET_TICKS_PER_SECOND, 0);
 
  790        $this->tickSleeper = 
new TimeTrackingSleeperHandler(Timings::$serverInterrupts);
 
  792        $this->signalHandler = 
new SignalHandler(
function() : 
void{
 
  793            $this->logger->info(
"Received signal interrupt, stopping the server");
 
  801                Path::join($dataPath, 
"worlds"),
 
  802                Path::join($dataPath, 
"players")
 
  804                if(!file_exists($neededPath)){
 
  805                    mkdir($neededPath, 0777);
 
  809            $this->dataPath = realpath($dataPath) . DIRECTORY_SEPARATOR;
 
  810            $this->pluginPath = realpath($pluginPath) . DIRECTORY_SEPARATOR;
 
  812            $this->logger->info(
"Loading server configuration");
 
  813            $pocketmineYmlPath = Path::join($this->dataPath, 
"pocketmine.yml");
 
  814            if(!file_exists($pocketmineYmlPath)){
 
  815                $content = Filesystem::fileGetContents(Path::join(\
pocketmine\RESOURCE_PATH, 
"pocketmine.yml"));
 
  816                if(VersionInfo::IS_DEVELOPMENT_BUILD){
 
  817                    $content = str_replace(
"preferred-channel: stable", 
"preferred-channel: beta", $content);
 
  819                @file_put_contents($pocketmineYmlPath, $content);
 
  822            $this->configGroup = 
new ServerConfigGroup(
 
  823                new Config($pocketmineYmlPath, Config::YAML, []),
 
  824                new Config(Path::join($this->dataPath, 
"server.properties"), Config::PROPERTIES, [
 
  825                    ServerProperties::MOTD => self::DEFAULT_SERVER_NAME,
 
  826                    ServerProperties::SERVER_PORT_IPV4 => self::DEFAULT_PORT_IPV4,
 
  827                    ServerProperties::SERVER_PORT_IPV6 => self::DEFAULT_PORT_IPV6,
 
  828                    ServerProperties::ENABLE_IPV6 => 
true,
 
  829                    ServerProperties::WHITELIST => 
false,
 
  830                    ServerProperties::MAX_PLAYERS => self::DEFAULT_MAX_PLAYERS,
 
  831                    ServerProperties::GAME_MODE => GameMode::SURVIVAL->name, 
 
  832                    ServerProperties::FORCE_GAME_MODE => 
false,
 
  833                    ServerProperties::HARDCORE => 
false,
 
  834                    ServerProperties::PVP => 
true,
 
  835                    ServerProperties::DIFFICULTY => World::DIFFICULTY_NORMAL,
 
  836                    ServerProperties::DEFAULT_WORLD_GENERATOR_SETTINGS => 
"",
 
  837                    ServerProperties::DEFAULT_WORLD_NAME => 
"world",
 
  838                    ServerProperties::DEFAULT_WORLD_SEED => 
"",
 
  839                    ServerProperties::DEFAULT_WORLD_GENERATOR => 
"DEFAULT",
 
  840                    ServerProperties::ENABLE_QUERY => 
true,
 
  841                    ServerProperties::AUTO_SAVE => 
true,
 
  842                    ServerProperties::VIEW_DISTANCE => self::DEFAULT_MAX_VIEW_DISTANCE,
 
  843                    ServerProperties::XBOX_AUTH => 
true,
 
  844                    ServerProperties::LANGUAGE => 
"eng" 
  848            $debugLogLevel = $this->configGroup->getPropertyInt(Yml::DEBUG_LEVEL, 1);
 
  849            if($this->logger instanceof MainLogger){
 
  850                $this->logger->setLogDebug($debugLogLevel > 1);
 
  853            $this->forceLanguage = $this->configGroup->getPropertyBool(Yml::SETTINGS_FORCE_LANGUAGE, 
false);
 
  854            $selectedLang = $this->configGroup->getConfigString(ServerProperties::LANGUAGE, $this->configGroup->getPropertyString(
"settings.language", Language::FALLBACK_LANGUAGE));
 
  856                $this->language = 
new Language($selectedLang);
 
  857            }
catch(LanguageNotFoundException $e){
 
  858                $this->logger->error($e->getMessage());
 
  860                    $this->language = 
new Language(Language::FALLBACK_LANGUAGE);
 
  861                }
catch(LanguageNotFoundException $e){
 
  862                    $this->logger->emergency(
"Fallback language \"" . Language::FALLBACK_LANGUAGE . 
"\" not found");
 
  867            $this->logger->info($this->language->translate(KnownTranslationFactory::language_selected($this->language->getName(), $this->language->getLang())));
 
  869            if(VersionInfo::IS_DEVELOPMENT_BUILD){
 
  870                if(!$this->configGroup->getPropertyBool(Yml::SETTINGS_ENABLE_DEV_BUILDS, 
false)){
 
  871                    $this->logger->emergency($this->language->translate(KnownTranslationFactory::pocketmine_server_devBuild_error1(VersionInfo::NAME)));
 
  872                    $this->logger->emergency($this->language->translate(KnownTranslationFactory::pocketmine_server_devBuild_error2()));
 
  873                    $this->logger->emergency($this->language->translate(KnownTranslationFactory::pocketmine_server_devBuild_error3()));
 
  874                    $this->logger->emergency($this->language->translate(KnownTranslationFactory::pocketmine_server_devBuild_error4(Yml::SETTINGS_ENABLE_DEV_BUILDS)));
 
  875                    $this->logger->emergency($this->language->translate(KnownTranslationFactory::pocketmine_server_devBuild_error5(VersionInfo::GITHUB_URL . 
"/releases")));
 
  876                    $this->forceShutdownExit();
 
  881                $this->logger->warning(str_repeat(
"-", 40));
 
  882                $this->logger->warning($this->language->translate(KnownTranslationFactory::pocketmine_server_devBuild_warning1(VersionInfo::NAME)));
 
  883                $this->logger->warning($this->language->translate(KnownTranslationFactory::pocketmine_server_devBuild_warning2()));
 
  884                $this->logger->warning($this->language->translate(KnownTranslationFactory::pocketmine_server_devBuild_warning3()));
 
  885                $this->logger->warning(str_repeat(
"-", 40));
 
  888            $this->memoryManager = 
new MemoryManager($this);
 
  890            $this->logger->info($this->language->translate(KnownTranslationFactory::pocketmine_server_start(TextFormat::AQUA . $this->getVersion() . TextFormat::RESET)));
 
  892            if(($poolSize = $this->configGroup->getPropertyString(Yml::SETTINGS_ASYNC_WORKERS, 
"auto")) === 
"auto"){
 
  894                $processors = Utils::getCoreCount() - 2;
 
  897                    $poolSize = max(1, $processors);
 
  900                $poolSize = max(1, (
int) $poolSize);
 
  903            TimingsHandler::setEnabled($this->configGroup->getPropertyBool(Yml::SETTINGS_ENABLE_PROFILING, 
false));
 
  904            $this->profilingTickRate = $this->configGroup->getPropertyInt(Yml::SETTINGS_PROFILE_REPORT_TRIGGER, self::TARGET_TICKS_PER_SECOND);
 
  906            $this->asyncPool = 
new AsyncPool($poolSize, max(-1, $this->configGroup->getPropertyInt(Yml::MEMORY_ASYNC_WORKER_HARD_LIMIT, 256)), $this->autoloader, $this->logger, $this->tickSleeper);
 
  907            $this->asyncPool->addWorkerStartHook(
function(
int $i) : 
void{
 
  908                if(TimingsHandler::isEnabled()){
 
  909                    $this->asyncPool->submitTaskToWorker(TimingsControlTask::setEnabled(
true), $i);
 
  912            TimingsHandler::getToggleCallbacks()->add(
function(
bool $enable) : 
void{
 
  913                foreach($this->asyncPool->getRunningWorkers() as $workerId){
 
  914                    $this->asyncPool->submitTaskToWorker(TimingsControlTask::setEnabled($enable), $workerId);
 
  917            TimingsHandler::getReloadCallbacks()->add(
function() : 
void{
 
  918                foreach($this->asyncPool->getRunningWorkers() as $workerId){
 
  919                    $this->asyncPool->submitTaskToWorker(TimingsControlTask::reload(), $workerId);
 
  922            TimingsHandler::getCollectCallbacks()->add(
function() : array{
 
  924                foreach($this->asyncPool->getRunningWorkers() as $workerId){
 
  926                    $resolver = 
new PromiseResolver();
 
  927                    $this->asyncPool->submitTaskToWorker(
new TimingsCollectionTask($resolver), $workerId);
 
  929                    $promises[] = $resolver->getPromise();
 
  935            $netCompressionThreshold = -1;
 
  936            if($this->configGroup->getPropertyInt(Yml::NETWORK_BATCH_THRESHOLD, 256) >= 0){
 
  937                $netCompressionThreshold = $this->configGroup->getPropertyInt(Yml::NETWORK_BATCH_THRESHOLD, 256);
 
  939            if($netCompressionThreshold < 0){
 
  940                $netCompressionThreshold = 
null;
 
  943            $netCompressionLevel = $this->configGroup->getPropertyInt(Yml::NETWORK_COMPRESSION_LEVEL, 6);
 
  944            if($netCompressionLevel < 1 || $netCompressionLevel > 9){
 
  945                $this->logger->warning(
"Invalid network compression level $netCompressionLevel set, setting to default 6");
 
  946                $netCompressionLevel = 6;
 
  948            ZlibCompressor::setInstance(
new ZlibCompressor($netCompressionLevel, $netCompressionThreshold, ZlibCompressor::DEFAULT_MAX_DECOMPRESSION_SIZE));
 
  950            $this->networkCompressionAsync = $this->configGroup->getPropertyBool(Yml::NETWORK_ASYNC_COMPRESSION, 
true);
 
  951            $this->networkCompressionAsyncThreshold = max(
 
  952                $this->configGroup->getPropertyInt(Yml::NETWORK_ASYNC_COMPRESSION_THRESHOLD, self::DEFAULT_ASYNC_COMPRESSION_THRESHOLD),
 
  953                $netCompressionThreshold ?? self::DEFAULT_ASYNC_COMPRESSION_THRESHOLD
 
  956            EncryptionContext::$ENABLED = $this->configGroup->getPropertyBool(Yml::NETWORK_ENABLE_ENCRYPTION, 
true);
 
  958            $this->doTitleTick = $this->configGroup->getPropertyBool(Yml::CONSOLE_TITLE_TICK, 
true) && Terminal::hasFormattingCodes();
 
  960            $this->operators = 
new Config(Path::join($this->dataPath, 
"ops.txt"), Config::ENUM);
 
  961            $this->whitelist = 
new Config(Path::join($this->dataPath, 
"white-list.txt"), Config::ENUM);
 
  963            $bannedTxt = Path::join($this->dataPath, 
"banned.txt");
 
  964            $bannedPlayersTxt = Path::join($this->dataPath, 
"banned-players.txt");
 
  965            if(file_exists($bannedTxt) && !file_exists($bannedPlayersTxt)){
 
  966                @rename($bannedTxt, $bannedPlayersTxt);
 
  968            @touch($bannedPlayersTxt);
 
  969            $this->banByName = 
new BanList($bannedPlayersTxt);
 
  970            $this->banByName->load();
 
  971            $bannedIpsTxt = Path::join($this->dataPath, 
"banned-ips.txt");
 
  972            @touch($bannedIpsTxt);
 
  973            $this->banByIP = 
new BanList($bannedIpsTxt);
 
  974            $this->banByIP->load();
 
  976            $this->maxPlayers = $this->configGroup->getConfigInt(ServerProperties::MAX_PLAYERS, self::DEFAULT_MAX_PLAYERS);
 
  978            $this->onlineMode = $this->configGroup->getConfigBool(ServerProperties::XBOX_AUTH, 
true);
 
  979            if($this->onlineMode){
 
  980                $this->logger->info($this->language->translate(KnownTranslationFactory::pocketmine_server_auth_enabled()));
 
  982                $this->logger->warning($this->language->translate(KnownTranslationFactory::pocketmine_server_auth_disabled()));
 
  983                $this->logger->warning($this->language->translate(KnownTranslationFactory::pocketmine_server_authWarning()));
 
  984                $this->logger->warning($this->language->translate(KnownTranslationFactory::pocketmine_server_authProperty_disabled()));
 
  987            $this->authKeyProvider = 
new AuthKeyProvider(
new \
PrefixedLogger($this->logger, 
"Minecraft Auth Key Provider"), $this->asyncPool);
 
  989            if($this->configGroup->getConfigBool(ServerProperties::HARDCORE, 
false) && $this->getDifficulty() < World::DIFFICULTY_HARD){
 
  990                $this->configGroup->setConfigInt(ServerProperties::DIFFICULTY, World::DIFFICULTY_HARD);
 
  993            @cli_set_process_title($this->getName() . 
" " . $this->getPocketMineVersion());
 
  995            $this->serverID = Utils::getMachineUniqueId($this->getIp() . $this->getPort());
 
  997            $this->logger->debug(
"Server unique id: " . $this->getServerUniqueId());
 
  998            $this->logger->debug(
"Machine unique id: " . Utils::getMachineUniqueId());
 
 1000            $this->network = 
new Network($this->logger);
 
 1001            $this->network->setName($this->getMotd());
 
 1003            $this->logger->info($this->language->translate(KnownTranslationFactory::pocketmine_server_info(
 
 1005                (VersionInfo::IS_DEVELOPMENT_BUILD ? TextFormat::YELLOW : 
"") . $this->getPocketMineVersion() . TextFormat::RESET
 
 1007            $this->logger->info($this->language->translate(KnownTranslationFactory::pocketmine_server_license($this->getName())));
 
 1009            DefaultPermissions::registerCorePermissions();
 
 1011            $this->commandMap = 
new SimpleCommandMap($this);
 
 1013            $this->craftingManager = CraftingManagerFromDataHelper::make(BedrockDataFiles::RECIPES);
 
 1015            $this->resourceManager = 
new ResourcePackManager(Path::join($this->dataPath, 
"resource_packs"), $this->logger);
 
 1017            $pluginGraylist = 
null;
 
 1018            $graylistFile = Path::join($this->dataPath, 
"plugin_list.yml");
 
 1019            if(!file_exists($graylistFile)){
 
 1020                copy(Path::join(\
pocketmine\RESOURCE_PATH, 
'plugin_list.yml'), $graylistFile);
 
 1023                $array = yaml_parse(Filesystem::fileGetContents($graylistFile));
 
 1024                if(!is_array($array)){
 
 1025                    throw new \InvalidArgumentException(
"Expected array for root, but have " . gettype($array));
 
 1027                $pluginGraylist = PluginGraylist::fromArray($array);
 
 1028            }
catch(\InvalidArgumentException $e){
 
 1029                $this->logger->emergency(
"Failed to load $graylistFile: " . $e->getMessage());
 
 1030                $this->forceShutdownExit();
 
 1033            $this->pluginManager = 
new PluginManager($this, $this->configGroup->getPropertyBool(Yml::PLUGINS_LEGACY_DATA_DIR, 
true) ? 
null : Path::join($this->dataPath, 
"plugin_data"), $pluginGraylist);
 
 1034            $this->pluginManager->registerInterface(
new PharPluginLoader($this->autoloader));
 
 1035            $this->pluginManager->registerInterface(
new ScriptPluginLoader());
 
 1037            $providerManager = 
new WorldProviderManager();
 
 1039                ($format = $providerManager->getProviderByName($formatName = $this->configGroup->getPropertyString(Yml::LEVEL_SETTINGS_DEFAULT_FORMAT, 
""))) !== 
null &&
 
 1040                $format instanceof WritableWorldProviderManagerEntry
 
 1042                $providerManager->setDefault($format);
 
 1043            }elseif($formatName !== 
""){
 
 1044                $this->logger->warning($this->language->translate(KnownTranslationFactory::pocketmine_level_badDefaultFormat($formatName)));
 
 1047            $this->worldManager = 
new WorldManager($this, Path::join($this->dataPath, 
"worlds"), $providerManager);
 
 1048            $this->worldManager->setAutoSave($this->configGroup->getConfigBool(ServerProperties::AUTO_SAVE, $this->worldManager->getAutoSave()));
 
 1049            $this->worldManager->setAutoSaveInterval($this->configGroup->getPropertyInt(Yml::TICKS_PER_AUTOSAVE, $this->worldManager->getAutoSaveInterval()));
 
 1051            $this->updater = 
new UpdateChecker($this, $this->configGroup->getPropertyString(Yml::AUTO_UPDATER_HOST, 
"update.pmmp.io"));
 
 1053            $this->queryInfo = 
new QueryInfo($this);
 
 1055            $this->playerDataProvider = 
new DatFilePlayerDataProvider(Path::join($this->dataPath, 
"players"));
 
 1057            register_shutdown_function($this->crashDump(...));
 
 1059            $loadErrorCount = 0;
 
 1060            $this->pluginManager->loadPlugins($this->pluginPath, $loadErrorCount);
 
 1061            if($loadErrorCount > 0){
 
 1062                $this->logger->emergency($this->language->translate(KnownTranslationFactory::pocketmine_plugin_someLoadErrors()));
 
 1063                $this->forceShutdownExit();
 
 1066            if(!$this->enablePlugins(PluginEnableOrder::STARTUP)){
 
 1067                $this->logger->emergency($this->language->translate(KnownTranslationFactory::pocketmine_plugin_someEnableErrors()));
 
 1068                $this->forceShutdownExit();
 
 1072            if(!$this->startupPrepareWorlds()){
 
 1073                $this->forceShutdownExit();
 
 1077            if(!$this->enablePlugins(PluginEnableOrder::POSTWORLD)){
 
 1078                $this->logger->emergency($this->language->translate(KnownTranslationFactory::pocketmine_plugin_someEnableErrors()));
 
 1079                $this->forceShutdownExit();
 
 1083            if(!$this->startupPrepareNetworkInterfaces()){
 
 1084                $this->forceShutdownExit();
 
 1088            if($this->configGroup->getPropertyBool(Yml::ANONYMOUS_STATISTICS_ENABLED, 
true)){
 
 1089                $this->sendUsageTicker = self::TICKS_PER_STATS_REPORT;
 
 1090                $this->sendUsage(SendUsageTask::TYPE_OPEN);
 
 1093            $this->configGroup->save();
 
 1095            $this->logger->info($this->language->translate(KnownTranslationFactory::pocketmine_server_defaultGameMode($this->getGamemode()->getTranslatableName())));
 
 1096            $highlight = TextFormat::AQUA;
 
 1097            $reset = TextFormat::RESET;
 
 1098            $github = VersionInfo::GITHUB_URL;
 
 1101                KnownTranslationFactory::pocketmine_server_url_discord(
"{$highlight}https://discord.pmmp.io{$reset}"),
 
 1102                KnownTranslationFactory::pocketmine_server_url_docs(
"{$highlight}https://doc.pmmp.io{$reset}"),
 
 1103                KnownTranslationFactory::pocketmine_server_url_sourceCode(
"{$highlight}{$github}{$reset}"),
 
 1104                KnownTranslationFactory::pocketmine_server_url_freePlugins(
"{$highlight}https://poggit.pmmp.io/plugins{$reset}"),
 
 1105                KnownTranslationFactory::pocketmine_server_url_donations(
"{$highlight}https://patreon.com/pocketminemp{$reset}"),
 
 1106                KnownTranslationFactory::pocketmine_server_url_translations(
"{$highlight}https://translate.pocketmine.net{$reset}"),
 
 1107                KnownTranslationFactory::pocketmine_server_url_bugReporting(
"{$highlight}{$github}/issues{$reset}")
 
 1109                $splash .= 
"- " . $this->language->translate($link) . 
"\n";
 
 1111            $this->logger->info($splash);
 
 1113            $this->logger->info($this->language->translate(KnownTranslationFactory::pocketmine_server_startFinished(strval(round(microtime(
true) - $this->startTime, 3)))));
 
 1115            $forwarder = 
new BroadcastLoggerForwarder($this, $this->logger, $this->language);
 
 1116            $this->subscribeToBroadcastChannel(self::BROADCAST_CHANNEL_ADMINISTRATIVE, $forwarder);
 
 1117            $this->subscribeToBroadcastChannel(self::BROADCAST_CHANNEL_USERS, $forwarder);
 
 1120            if($this->configGroup->getPropertyBool(Yml::CONSOLE_ENABLE_INPUT, 
true)){
 
 1121                $this->console = 
new ConsoleReaderChildProcessDaemon($this->logger);
 
 1124            $this->tickProcessor();
 
 1125            $this->forceShutdown();
 
 1126        }
catch(\Throwable $e){
 
 1127            $this->exceptionHandler($e);
 
 1131    private function startupPrepareWorlds() : bool{
 
 1132        $getGenerator = function(string $generatorName, string $generatorOptions, string $worldName) : ?string{
 
 1133            $generatorEntry = GeneratorManager::getInstance()->getGenerator($generatorName);
 
 1134            if($generatorEntry === 
null){
 
 1135                $this->logger->error($this->language->translate(KnownTranslationFactory::pocketmine_level_generationError(
 
 1137                    KnownTranslationFactory::pocketmine_level_unknownGenerator($generatorName)
 
 1142                $generatorEntry->validateGeneratorOptions($generatorOptions);
 
 1143            }
catch(InvalidGeneratorOptionsException $e){
 
 1144                $this->logger->error($this->language->translate(KnownTranslationFactory::pocketmine_level_generationError(
 
 1146                    KnownTranslationFactory::pocketmine_level_invalidGeneratorOptions($generatorOptions, $generatorName, $e->getMessage())
 
 1150            return $generatorEntry->getGeneratorClass();
 
 1153        $anyWorldFailedToLoad = 
false;
 
 1155        foreach(Utils::promoteKeys((array) $this->configGroup->getProperty(Yml::WORLDS, [])) as $name => $options){
 
 1156            if(!is_string($name)){
 
 1160            if($options === 
null){
 
 1162            }elseif(!is_array($options)){
 
 1166            if(!$this->worldManager->loadWorld($name, 
true)){
 
 1167                if($this->worldManager->isWorldGenerated($name)){
 
 1169                    $anyWorldFailedToLoad = true;
 
 1172                $creationOptions = WorldCreationOptions::create();
 
 1175                $generatorName = $options[
"generator"] ?? 
"default";
 
 1176                $generatorOptions = isset($options[
"preset"]) && is_string($options[
"preset"]) ? $options[
"preset"] : 
"";
 
 1178                $generatorClass = $getGenerator($generatorName, $generatorOptions, $name);
 
 1179                if($generatorClass === 
null){
 
 1180                    $anyWorldFailedToLoad = 
true;
 
 1183                $creationOptions->setGeneratorClass($generatorClass);
 
 1184                $creationOptions->setGeneratorOptions($generatorOptions);
 
 1186                $creationOptions->setDifficulty($this->getDifficulty());
 
 1187                if(isset($options[
"difficulty"]) && is_string($options[
"difficulty"])){
 
 1188                    $creationOptions->setDifficulty(World::getDifficultyFromString($options[
"difficulty"]));
 
 1191                if(isset($options[
"seed"])){
 
 1192                    $convertedSeed = Generator::convertSeed((
string) ($options[
"seed"] ?? 
""));
 
 1193                    if($convertedSeed !== 
null){
 
 1194                        $creationOptions->setSeed($convertedSeed);
 
 1198                $this->worldManager->generateWorld($name, $creationOptions);
 
 1202        if($this->worldManager->getDefaultWorld() === 
null){
 
 1203            $default = $this->configGroup->getConfigString(ServerProperties::DEFAULT_WORLD_NAME, 
"world");
 
 1204            if(trim($default) === 
""){
 
 1205                $this->logger->warning(
"level-name cannot be null, using default");
 
 1207                $this->configGroup->setConfigString(ServerProperties::DEFAULT_WORLD_NAME, 
"world");
 
 1209            if(!$this->worldManager->loadWorld($default, 
true)){
 
 1210                if($this->worldManager->isWorldGenerated($default)){
 
 1211                    $this->logger->emergency($this->language->translate(KnownTranslationFactory::pocketmine_level_defaultError()));
 
 1215                $generatorName = $this->configGroup->getConfigString(ServerProperties::DEFAULT_WORLD_GENERATOR);
 
 1216                $generatorOptions = $this->configGroup->getConfigString(ServerProperties::DEFAULT_WORLD_GENERATOR_SETTINGS);
 
 1217                $generatorClass = $getGenerator($generatorName, $generatorOptions, $default);
 
 1219                if($generatorClass === 
null){
 
 1220                    $this->logger->emergency($this->language->translate(KnownTranslationFactory::pocketmine_level_defaultError()));
 
 1223                $creationOptions = WorldCreationOptions::create()
 
 1224                    ->setGeneratorClass($generatorClass)
 
 1225                    ->setGeneratorOptions($generatorOptions);
 
 1226                $convertedSeed = Generator::convertSeed($this->configGroup->getConfigString(ServerProperties::DEFAULT_WORLD_SEED));
 
 1227                if($convertedSeed !== 
null){
 
 1228                    $creationOptions->setSeed($convertedSeed);
 
 1230                $creationOptions->setDifficulty($this->getDifficulty());
 
 1231                $this->worldManager->generateWorld($default, $creationOptions);
 
 1234            $world = $this->worldManager->getWorldByName($default);
 
 1235            if($world === 
null){
 
 1236                throw new AssumptionFailedError(
"We just loaded/generated the default world, so it must exist");
 
 1238            $this->worldManager->setDefaultWorld($world);
 
 1241        return !$anyWorldFailedToLoad;
 
 1244    private function startupPrepareConnectableNetworkInterfaces(
 
 1249        PacketBroadcaster $packetBroadcaster,
 
 1250        EntityEventBroadcaster $entityEventBroadcaster,
 
 1251        TypeConverter $typeConverter
 
 1253        $prettyIp = $ipV6 ? 
"[$ip]" : $ip;
 
 1255            $rakLibRegistered = $this->network->registerInterface(
new RakLibInterface($this, $ip, $port, $ipV6, $packetBroadcaster, $entityEventBroadcaster, $typeConverter));
 
 1256        }
catch(NetworkInterfaceStartException $e){
 
 1257            $this->logger->emergency($this->language->translate(KnownTranslationFactory::pocketmine_server_networkStartFailed(
 
 1264        if($rakLibRegistered){
 
 1265            $this->logger->info($this->language->translate(KnownTranslationFactory::pocketmine_server_networkStart($prettyIp, (
string) $port)));
 
 1268            if(!$rakLibRegistered){
 
 1271                $this->network->registerInterface(
new DedicatedQueryNetworkInterface($ip, $port, $ipV6, 
new \
PrefixedLogger($this->logger, 
"Dedicated Query Interface")));
 
 1273            $this->logger->info($this->language->translate(KnownTranslationFactory::pocketmine_server_query_running($prettyIp, (
string) $port)));
 
 1278    private function startupPrepareNetworkInterfaces() : bool{
 
 1279        $useQuery = $this->configGroup->getConfigBool(ServerProperties::ENABLE_QUERY, true);
 
 1281        $typeConverter = TypeConverter::getInstance();
 
 1282        $packetBroadcaster = 
new StandardPacketBroadcaster($this);
 
 1283        $entityEventBroadcaster = 
new StandardEntityEventBroadcaster($packetBroadcaster, $typeConverter);
 
 1286            !$this->startupPrepareConnectableNetworkInterfaces($this->getIp(), $this->getPort(), 
false, $useQuery, $packetBroadcaster, $entityEventBroadcaster, $typeConverter) ||
 
 1288                $this->configGroup->getConfigBool(ServerProperties::ENABLE_IPV6, 
true) &&
 
 1289                !$this->startupPrepareConnectableNetworkInterfaces($this->getIpV6(), $this->getPortV6(), 
true, $useQuery, $packetBroadcaster, $entityEventBroadcaster, $typeConverter)
 
 1296            $this->network->registerRawPacketHandler(
new QueryHandler($this));
 
 1299        foreach($this->getIPBans()->getEntries() as $entry){
 
 1300            $this->network->blockAddress($entry->getName(), -1);
 
 1303        if($this->configGroup->getPropertyBool(Yml::NETWORK_UPNP_FORWARDING, 
false)){
 
 1304            $this->network->registerInterface(
new UPnPNetworkInterface($this->logger, Internet::getInternalIP(), $this->getPort()));
 
 1315        $this->broadcastSubscribers[$channelId][spl_object_id($subscriber)] = $subscriber;
 
 
 1322        if(isset($this->broadcastSubscribers[$channelId][spl_object_id($subscriber)])){
 
 1323            if(count($this->broadcastSubscribers[$channelId]) === 1){
 
 1324                unset($this->broadcastSubscribers[$channelId]);
 
 1326                unset($this->broadcastSubscribers[$channelId][spl_object_id($subscriber)]);
 
 
 1335        foreach(
Utils::stringifyKeys($this->broadcastSubscribers) as $channelId => $recipients){
 
 1336            $this->unsubscribeFromBroadcastChannel($channelId, $subscriber);
 
 
 1347        return $this->broadcastSubscribers[$channelId] ?? [];
 
 
 1354        $recipients = $recipients ?? $this->getBroadcastChannelSubscribers(self::BROADCAST_CHANNEL_USERS);
 
 1356        foreach($recipients as $recipient){
 
 1357            $recipient->sendMessage($message);
 
 1360        return count($recipients);
 
 
 1366    private function getPlayerBroadcastSubscribers(
string $channelId) : array{
 
 1369        foreach($this->broadcastSubscribers[$channelId] as $subscriber){
 
 1370            if($subscriber instanceof Player){
 
 1371                $players[spl_object_id($subscriber)] = $subscriber;
 
 1380    public function broadcastTip(
string $tip, ?array $recipients = 
null) : int{
 
 1381        $recipients = $recipients ?? $this->getPlayerBroadcastSubscribers(self::BROADCAST_CHANNEL_USERS);
 
 1383        foreach($recipients as $recipient){
 
 1384            $recipient->sendTip($tip);
 
 1387        return count($recipients);
 
 
 1394        $recipients = $recipients ?? $this->getPlayerBroadcastSubscribers(self::BROADCAST_CHANNEL_USERS);
 
 1396        foreach($recipients as $recipient){
 
 1397            $recipient->sendPopup($popup);
 
 1400        return count($recipients);
 
 
 1409    public function broadcastTitle(
string $title, 
string $subtitle = 
"", 
int $fadeIn = -1, 
int $stay = -1, 
int $fadeOut = -1, ?array $recipients = 
null) : int{
 
 1410        $recipients = $recipients ?? $this->getPlayerBroadcastSubscribers(self::BROADCAST_CHANNEL_USERS);
 
 1412        foreach($recipients as $recipient){
 
 1413            $recipient->sendTitle($title, $subtitle, $fadeIn, $stay, $fadeOut);
 
 1416        return count($recipients);
 
 
 1432    public function prepareBatch(
string $buffer, Compressor $compressor, ?
bool $sync = 
null, ?TimingsHandler $timings = 
null) : CompressBatchPromise|string{
 
 1433        $timings ??= Timings::$playerNetworkSendCompress;
 
 1435            $timings->startTiming();
 
 1437            $threshold = $compressor->getCompressionThreshold();
 
 1438            if($threshold === 
null || strlen($buffer) < $compressor->getCompressionThreshold()){
 
 1439                $compressionType = CompressionAlgorithm::NONE;
 
 1440                $compressed = $buffer;
 
 1443                $sync ??= !$this->networkCompressionAsync;
 
 1445                if(!$sync && strlen($buffer) >= $this->networkCompressionAsyncThreshold){
 
 1446                    $promise = 
new CompressBatchPromise();
 
 1447                    $task = 
new CompressBatchTask($buffer, $promise, $compressor);
 
 1448                    $this->asyncPool->submitTask($task);
 
 1452                $compressionType = $compressor->getNetworkId();
 
 1453                $compressed = $compressor->compress($buffer);
 
 1456            return chr($compressionType) . $compressed;
 
 1458            $timings->stopTiming();
 
 1462    public function enablePlugins(PluginEnableOrder $type) : bool{
 
 1464        foreach($this->pluginManager->getPlugins() as $plugin){
 
 1465            if(!$plugin->isEnabled() && $plugin->getDescription()->getOrder() === $type){
 
 1466                if(!$this->pluginManager->enablePlugin($plugin)){
 
 1467                    $allSuccess = 
false;
 
 1472        if($type === PluginEnableOrder::POSTWORLD){
 
 1473            $this->commandMap->registerServerAliases();
 
 1486            if($ev->isCancelled()){
 
 1490            $commandLine = $ev->getCommand();
 
 1493        return $this->commandMap->dispatch($sender, $commandLine);
 
 
 1500        if($this->isRunning){
 
 1501            $this->isRunning = 
false;
 
 1502            $this->signalHandler->unregister();
 
 
 1506    private function forceShutdownExit() : void{
 
 1507        $this->forceShutdown();
 
 1508        Process::kill(Process::pid());
 
 1511    public function forceShutdown() : void{
 
 1512        if($this->hasStopped){
 
 1516        if($this->doTitleTick){
 
 1520        if($this->isRunning){
 
 1521            $this->logger->emergency($this->language->translate(KnownTranslationFactory::pocketmine_server_forcingShutdown()));
 
 1524            if(!$this->isRunning()){
 
 1525                $this->sendUsage(SendUsageTask::TYPE_CLOSE);
 
 1528            $this->hasStopped = 
true;
 
 1532            if(isset($this->pluginManager)){
 
 1533                $this->logger->debug(
"Disabling all plugins");
 
 1534                $this->pluginManager->disablePlugins();
 
 1537            if(isset($this->network)){
 
 1538                $this->network->getSessionManager()->close($this->configGroup->getPropertyString(Yml::SETTINGS_SHUTDOWN_MESSAGE, 
"Server closed"));
 
 1541            if(isset($this->worldManager)){
 
 1542                $this->logger->debug(
"Unloading all worlds");
 
 1543                foreach($this->worldManager->getWorlds() as $world){
 
 1544                    $this->worldManager->unloadWorld($world, 
true);
 
 1548            $this->logger->debug(
"Removing event handlers");
 
 1549            HandlerListManager::global()->unregisterAll();
 
 1551            if(isset($this->asyncPool)){
 
 1552                $this->logger->debug(
"Shutting down async task worker pool");
 
 1553                $this->asyncPool->shutdown();
 
 1556            if(isset($this->configGroup)){
 
 1557                $this->logger->debug(
"Saving properties");
 
 1558                $this->configGroup->save();
 
 1561            if($this->console !== 
null){
 
 1562                $this->logger->debug(
"Closing console");
 
 1563                $this->console->quit();
 
 1566            if(isset($this->network)){
 
 1567                $this->logger->debug(
"Stopping network interfaces");
 
 1568                foreach($this->network->getInterfaces() as $interface){
 
 1569                    $this->logger->debug(
"Stopping network interface " . get_class($interface));
 
 1570                    $this->network->unregisterInterface($interface);
 
 1573        }
catch(\Throwable $e){
 
 1574            $this->logger->logException($e);
 
 1575            $this->logger->emergency(
"Crashed while crashing, killing process");
 
 1576            @Process::kill(Process::pid());
 
 1581    public function getQueryInformation() : QueryInfo{
 
 1582        return $this->queryInfo;
 
 1590        while(@ob_end_flush()){}
 
 1593        if($trace === 
null){
 
 1594            $trace = $e->getTrace();
 
 1600        $this->logger->logException($e, $trace);
 
 1602        if($e instanceof ThreadCrashException){
 
 1603            $info = $e->getCrashInfo();
 
 1604            $type = $info->getType();
 
 1605            $errstr = $info->getMessage();
 
 1606            $errfile = $info->getFile();
 
 1607            $errline = $info->getLine();
 
 1608            $printableTrace = $info->getTrace();
 
 1609            $thread = $info->getThreadName();
 
 1611            $type = get_class($e);
 
 1612            $errstr = $e->getMessage();
 
 1613            $errfile = $e->getFile();
 
 1614            $errline = $e->getLine();
 
 1615            $printableTrace = Utils::printableTraceWithMetadata($trace);
 
 1619        $errstr = preg_replace(
'/\s+/', 
' ', trim($errstr));
 
 1623            "message" => $errstr,
 
 1624            "fullFile" => $errfile,
 
 1625            "file" => Filesystem::cleanPath($errfile),
 
 1627            "trace" => $printableTrace,
 
 1631        global $lastExceptionError, $lastError;
 
 1632        $lastExceptionError = $lastError;
 
 
 1636    private function writeCrashDumpFile(CrashDump $dump) : string{
 
 1637        $crashFolder = Path::join($this->dataPath, 
"crashdumps");
 
 1638        if(!is_dir($crashFolder)){
 
 1639            mkdir($crashFolder);
 
 1641        $crashDumpPath = Path::join($crashFolder, date(
"Y-m-d_H.i.s_T", (
int) $dump->getData()->time) . 
".log");
 
 1643        $fp = @fopen($crashDumpPath, 
"wb");
 
 1644        if(!is_resource($fp)){
 
 1645            throw new \RuntimeException(
"Unable to open new file to generate crashdump");
 
 1647        $writer = 
new CrashDumpRenderer($fp, $dump->getData());
 
 1648        $writer->renderHumanReadable();
 
 1649        $dump->encodeData($writer);
 
 1652        return $crashDumpPath;
 
 1655    public function crashDump() : void{
 
 1656        while(@ob_end_flush()){}
 
 1657        if(!$this->isRunning){
 
 1660        if($this->sendUsageTicker > 0){
 
 1661            $this->sendUsage(SendUsageTask::TYPE_CLOSE);
 
 1663        $this->hasStopped = 
false;
 
 1665        ini_set(
"error_reporting", 
'0');
 
 1666        ini_set(
"memory_limit", 
'-1'); 
 
 1668            $this->logger->emergency($this->language->translate(KnownTranslationFactory::pocketmine_crash_create()));
 
 1669            $dump = 
new CrashDump($this, $this->pluginManager ?? 
null);
 
 1671            $crashDumpPath = $this->writeCrashDumpFile($dump);
 
 1673            $this->logger->emergency($this->language->translate(KnownTranslationFactory::pocketmine_crash_submit($crashDumpPath)));
 
 1675            if($this->configGroup->getPropertyBool(Yml::AUTO_REPORT_ENABLED, 
true)){
 
 1678                $stamp = Path::join($this->dataPath, 
"crashdumps", 
".last_crash");
 
 1679                $crashInterval = 120; 
 
 1680                if(($lastReportTime = @filemtime($stamp)) !== 
false && $lastReportTime + $crashInterval >= time()){
 
 1682                    $this->logger->debug(
"Not sending crashdump due to last crash less than $crashInterval seconds ago");
 
 1686                if($dump->getData()->error[
"type"] === \ParseError::class){
 
 1690                if(strrpos(VersionInfo::GIT_HASH(), 
"-dirty") !== 
false || VersionInfo::GIT_HASH() === str_repeat(
"00", 20)){
 
 1691                    $this->logger->debug(
"Not sending crashdump due to locally modified");
 
 1696                    $url = ($this->configGroup->getPropertyBool(Yml::AUTO_REPORT_USE_HTTPS, 
true) ? 
"https" : 
"http") . 
"://" . $this->configGroup->getPropertyString(Yml::AUTO_REPORT_HOST, 
"crash.pmmp.io") . 
"/submit/api";
 
 1697                    $postUrlError = 
"Unknown error";
 
 1698                    $reply = Internet::postURL($url, [
 
 1700                        "name" => $this->getName() . 
" " . $this->getPocketMineVersion(),
 
 1702                        "reportPaste" => base64_encode($dump->getEncodedData())
 
 1703                    ], 10, [], $postUrlError);
 
 1705                    if($reply !== 
null && is_object($data = json_decode($reply->getBody()))){
 
 1706                        if(isset($data->crashId) && is_int($data->crashId) && isset($data->crashUrl) && is_string($data->crashUrl)){
 
 1707                            $reportId = $data->crashId;
 
 1708                            $reportUrl = $data->crashUrl;
 
 1709                            $this->logger->emergency($this->language->translate(KnownTranslationFactory::pocketmine_crash_archive($reportUrl, (
string) $reportId)));
 
 1710                        }elseif(isset($data->error) && is_string($data->error)){
 
 1711                            $this->logger->emergency(
"Automatic crash report submission failed: $data->error");
 
 1713                            $this->logger->emergency(
"Invalid JSON response received from crash archive: " . $reply->getBody());
 
 1716                        $this->logger->emergency(
"Failed to communicate with crash archive: $postUrlError");
 
 1720        }
catch(\Throwable $e){
 
 1721            $this->logger->logException($e);
 
 1723                $this->logger->critical($this->language->translate(KnownTranslationFactory::pocketmine_crash_error($e->getMessage())));
 
 1724            }
catch(\Throwable $e){}
 
 1727        $this->forceShutdown();
 
 1728        $this->isRunning = 
false;
 
 1731        $uptime = time() - ((int) $this->startTime);
 
 1733        $spacing = $minUptime - $uptime;
 
 1735            echo 
"--- Uptime {$uptime}s - waiting {$spacing}s to throttle automatic restart (you can kill the process safely now) ---" . PHP_EOL;
 
 1738        @Process::kill(Process::pid());
 
 1750        return $this->tickSleeper;
 
 1753    private function tickProcessor() : void{
 
 1754        $this->nextTick = microtime(true);
 
 1756        while($this->isRunning){
 
 1760            $this->tickSleeper->sleepUntil($this->nextTick);
 
 1764    public function addOnlinePlayer(Player $player) : bool{
 
 1765        $ev = new PlayerLoginEvent($player, 
"Plugin reason");
 
 1767        if($ev->isCancelled() || !$player->isConnected()){
 
 1768            $player->disconnect($ev->getKickMessage());
 
 1773        $session = $player->getNetworkSession();
 
 1774        $position = $player->getPosition();
 
 1775        $this->logger->info($this->language->translate(KnownTranslationFactory::pocketmine_player_logIn(
 
 1776            TextFormat::AQUA . $player->getName() . TextFormat::RESET,
 
 1778            (
string) $session->getPort(),
 
 1779            (
string) $player->getId(),
 
 1780            $position->getWorld()->getDisplayName(),
 
 1781            (
string) round($position->x, 4),
 
 1782            (
string) round($position->y, 4),
 
 1783            (
string) round($position->z, 4)
 
 1786        foreach($this->playerList as $p){
 
 1787            $p->getNetworkSession()->onPlayerAdded($player);
 
 1789        $rawUUID = $player->getUniqueId()->getBytes();
 
 1790        $this->playerList[$rawUUID] = $player;
 
 1792        if($this->sendUsageTicker > 0){
 
 1793            $this->uniquePlayers[$rawUUID] = $rawUUID;
 
 1799    public function removeOnlinePlayer(Player $player) : void{
 
 1800        if(isset($this->playerList[$rawUUID = $player->getUniqueId()->getBytes()])){
 
 1801            unset($this->playerList[$rawUUID]);
 
 1802            foreach($this->playerList as $p){
 
 1803                $p->getNetworkSession()->onPlayerRemoved($player);
 
 1808    public function sendUsage(
int $type = SendUsageTask::TYPE_STATUS) : void{
 
 1809        if($this->configGroup->getPropertyBool(Yml::ANONYMOUS_STATISTICS_ENABLED, true)){
 
 1810            $this->asyncPool->submitTask(
new SendUsageTask($this, $type, $this->uniquePlayers));
 
 1812        $this->uniquePlayers = [];
 
 1815    public function getLanguage() : Language{
 
 1816        return $this->language;
 
 1819    public function isLanguageForced() : bool{
 
 1820        return $this->forceLanguage;
 
 1826    public function getAuthKeyProvider() : AuthKeyProvider{
 
 1827        return $this->authKeyProvider;
 
 1830    public function getNetwork() : Network{
 
 1831        return $this->network;
 
 1834    public function getMemoryManager() : MemoryManager{
 
 1835        return $this->memoryManager;
 
 1838    private function titleTick() : void{
 
 1839        Timings::$titleTick->startTiming();
 
 1841        $u = Process::getAdvancedMemoryUsage();
 
 1842        $usage = sprintf(
"%g/%g/%g MB @ %d threads", round(($u[0] / 1024) / 1024, 2), round(($u[1] / 1024) / 1024, 2), round(($u[2] / 1024) / 1024, 2), Process::getThreadCount());
 
 1844        $online = count($this->playerList);
 
 1845        $connecting = $this->network->getConnectionCount() - $online;
 
 1846        $bandwidthStats = $this->network->getBandwidthTracker();
 
 1848        echo 
"\x1b]0;" . $this->getName() . 
" " .
 
 1849            $this->getPocketMineVersion() .
 
 1850            " | Online $online/" . $this->maxPlayers .
 
 1851            ($connecting > 0 ? 
" (+$connecting connecting)" : 
"") .
 
 1852            " | Memory " . $usage .
 
 1853            " | U " . round($bandwidthStats->getSend()->getAverageBytes() / 1024, 2) .
 
 1854            " D " . round($bandwidthStats->getReceive()->getAverageBytes() / 1024, 2) .
 
 1855            " kB/s | TPS " . $this->getTicksPerSecondAverage() .
 
 1856            " | Load " . $this->getTickUsageAverage() . 
"%\x07";
 
 1858        Timings::$titleTick->stopTiming();
 
 1864    private function tick() : void{
 
 1865        $tickTime = microtime(true);
 
 1866        if(($tickTime - $this->nextTick) < -0.025){ 
 
 1870        Timings::$serverTick->startTiming();
 
 1872        ++$this->tickCounter;
 
 1874        Timings::$scheduler->startTiming();
 
 1875        $this->pluginManager->tickSchedulers($this->tickCounter);
 
 1876        Timings::$scheduler->stopTiming();
 
 1878        Timings::$schedulerAsync->startTiming();
 
 1879        $this->asyncPool->collectTasks();
 
 1880        Timings::$schedulerAsync->stopTiming();
 
 1882        $this->worldManager->tick($this->tickCounter);
 
 1884        Timings::$connection->startTiming();
 
 1885        $this->network->tick();
 
 1886        Timings::$connection->stopTiming();
 
 1888        if(($this->tickCounter % self::TARGET_TICKS_PER_SECOND) === 0){
 
 1889            if($this->doTitleTick){
 
 1892            $this->currentTPS = self::TARGET_TICKS_PER_SECOND;
 
 1893            $this->currentUse = 0;
 
 1895            $queryRegenerateEvent = 
new QueryRegenerateEvent(
new QueryInfo($this));
 
 1896            $queryRegenerateEvent->call();
 
 1897            $this->queryInfo = $queryRegenerateEvent->getQueryInfo();
 
 1899            $this->network->updateName();
 
 1900            $this->network->getBandwidthTracker()->rotateAverageHistory();
 
 1903        if($this->sendUsageTicker > 0 && --$this->sendUsageTicker === 0){
 
 1904            $this->sendUsageTicker = self::TICKS_PER_STATS_REPORT;
 
 1905            $this->sendUsage(SendUsageTask::TYPE_STATUS);
 
 1908        if(($this->tickCounter % self::TICKS_PER_WORLD_CACHE_CLEAR) === 0){
 
 1909            foreach($this->worldManager->getWorlds() as $world){
 
 1910                $world->clearCache();
 
 1914        if(($this->tickCounter % self::TICKS_PER_TPS_OVERLOAD_WARNING) === 0 && $this->getTicksPerSecondAverage() < self::TPS_OVERLOAD_WARNING_THRESHOLD){
 
 1915            $this->logger->warning($this->language->translate(KnownTranslationFactory::pocketmine_server_tickOverload()));
 
 1918        $this->memoryManager->check();
 
 1920        if($this->console !== 
null){
 
 1921            Timings::$serverCommand->startTiming();
 
 1922            while(($line = $this->console->readLine()) !== 
null){
 
 1923                $this->consoleSender ??= 
new ConsoleCommandSender($this, $this->language);
 
 1924                $this->dispatchCommand($this->consoleSender, $line);
 
 1926            Timings::$serverCommand->stopTiming();
 
 1929        Timings::$serverTick->stopTiming();
 
 1931        $now = microtime(
true);
 
 1932        $totalTickTimeSeconds = $now - $tickTime + ($this->tickSleeper->getNotificationProcessingTime() / 1_000_000_000);
 
 1933        $this->currentTPS = min(self::TARGET_TICKS_PER_SECOND, 1 / max(0.001, $totalTickTimeSeconds));
 
 1934        $this->currentUse = min(1, $totalTickTimeSeconds / self::TARGET_SECONDS_PER_TICK);
 
 1936        TimingsHandler::tick($this->currentTPS <= $this->profilingTickRate);
 
 1938        $idx = $this->tickCounter % self::TARGET_TICKS_PER_SECOND;
 
 1939        $this->tickAverage[$idx] = $this->currentTPS;
 
 1940        $this->useAverage[$idx] = $this->currentUse;
 
 1941        $this->tickSleeper->resetNotificationProcessingTime();
 
 1943        if(($this->nextTick - $tickTime) < -1){
 
 1944            $this->nextTick = $tickTime;
 
 1946            $this->nextTick += self::TARGET_SECONDS_PER_TICK;