144    private const INCOMING_PACKET_BATCH_PER_TICK = 2; 
 
  145    private const INCOMING_PACKET_BATCH_BUFFER_TICKS = 100; 
 
  147    private const INCOMING_GAME_PACKETS_PER_TICK = 2;
 
  148    private const INCOMING_GAME_PACKETS_BUFFER_TICKS = 100;
 
  150    private const INCOMING_PACKET_BATCH_HARD_LIMIT = 300;
 
  155    private \PrefixedLogger $logger;
 
  156    private ?
Player $player = 
null;
 
  158    private ?
int $ping = 
null;
 
  162    private bool $connected = 
true;
 
  163    private bool $disconnectGuard = 
false;
 
  164    private bool $loggedIn = 
false;
 
  165    private bool $authenticated = 
false;
 
  166    private int $connectTime;
 
  167    private ?
CompoundTag $cachedOfflinePlayerData = 
null;
 
  175    private array $sendBuffer = [];
 
  180    private array $sendBufferAckPromises = [];
 
  183    private \SplQueue $compressedQueue;
 
  184    private bool $forceAsyncCompression = 
true;
 
  185    private bool $enableCompression = 
false; 
 
  187    private int $nextAckReceiptId = 0;
 
  192    private array $ackPromisesByReceiptId = [];
 
  202    private string $noisyPacketBuffer = 
"";
 
  203    private int $noisyPacketsDropped = 0;
 
  205    public function __construct(
 
  217        $this->logger = new \PrefixedLogger($this->
server->getLogger(), $this->getLogPrefix());
 
  219        $this->compressedQueue = new \SplQueue();
 
  223        $this->connectTime = time();
 
  224        $this->packetBatchLimiter = 
new PacketRateLimiter(
"Packet Batches", self::INCOMING_PACKET_BATCH_PER_TICK, self::INCOMING_PACKET_BATCH_BUFFER_TICKS);
 
  225        $this->gamePacketLimiter = 
new PacketRateLimiter(
"Game Packets", self::INCOMING_GAME_PACKETS_PER_TICK, self::INCOMING_GAME_PACKETS_BUFFER_TICKS);
 
  229            $this->onSessionStartSuccess(...)
 
  232        $this->manager->add($this);
 
  233        $this->logger->info($this->
server->getLanguage()->translate(KnownTranslationFactory::pocketmine_network_session_open()));
 
  236    private function getLogPrefix() : 
string{
 
  237        return "NetworkSession: " . $this->getDisplayName();
 
  240    public function getLogger() : \
Logger{
 
  241        return $this->logger;
 
  244    private function onSessionStartSuccess() : 
void{
 
  245        $this->logger->debug(
"Session start handshake completed, awaiting login packet");
 
  246        $this->flushGamePacketQueue();
 
  247        $this->enableCompression = 
true;
 
  253                $this->logger->info($this->
server->getLanguage()->translate(KnownTranslationFactory::pocketmine_network_session_playerName(TextFormat::AQUA . $info->getUsername() . TextFormat::RESET)));
 
  254                $this->logger->setPrefix($this->getLogPrefix());
 
  255                $this->manager->markLoginReceived($this);
 
  257            $this->setAuthenticationStatus(...)
 
  261    protected function createPlayer() : 
void{
 
  262        $this->
server->createPlayer($this, $this->info, $this->authenticated, $this->cachedOfflinePlayerData)->onCompletion(
 
  263            $this->onPlayerCreated(...),
 
  266                $this->disconnectWithError(
 
  267                    reason: 
"Failed to create player",
 
  268                    disconnectScreenMessage: KnownTranslationFactory::pocketmine_disconnect_error_internal()
 
  274    private function onPlayerCreated(
Player $player) : 
void{
 
  275        if(!$this->isConnected()){
 
  279        $this->player = $player;
 
  280        if(!$this->
server->addOnlinePlayer($player)){
 
  286        $effectManager = $this->player->getEffects();
 
  287        $effectManager->getEffectAddHooks()->add($effectAddHook = 
function(
EffectInstance $effect, 
bool $replacesOldEffect) : 
void{
 
  288            $this->entityEventBroadcaster->onEntityEffectAdded([$this], $this->player, $effect, $replacesOldEffect);
 
  290        $effectManager->getEffectRemoveHooks()->add($effectRemoveHook = 
function(
EffectInstance $effect) : 
void{
 
  291            $this->entityEventBroadcaster->onEntityEffectRemoved([$this], $this->player, $effect);
 
  293        $this->disposeHooks->add(
static function() use ($effectManager, $effectAddHook, $effectRemoveHook) : 
void{
 
  294            $effectManager->getEffectAddHooks()->remove($effectAddHook);
 
  295            $effectManager->getEffectRemoveHooks()->remove($effectRemoveHook);
 
  298        $permissionHooks = $this->player->getPermissionRecalculationCallbacks();
 
  299        $permissionHooks->add($permHook = 
function() : 
void{
 
  300            $this->logger->debug(
"Syncing available commands and abilities/permissions due to permission recalculation");
 
  301            $this->syncAbilities($this->player);
 
  302            $this->syncAvailableCommands();
 
  304        $this->disposeHooks->add(
static function() use ($permissionHooks, $permHook) : 
void{
 
  305            $permissionHooks->remove($permHook);
 
  307        $this->beginSpawnSequence();
 
  310    public function getPlayer() : ?
Player{
 
  311        return $this->player;
 
  314    public function getPlayerInfo() : ?
PlayerInfo{
 
  318    public function isConnected() : 
bool{
 
  319        return $this->connected && !$this->disconnectGuard;
 
  322    public function getIp() : 
string{
 
  326    public function getPort() : 
int{
 
  330    public function getDisplayName() : 
string{
 
  331        return $this->info !== 
null ? $this->info->getUsername() : $this->ip . 
" " . $this->port;
 
  344    public function updatePing(
int $ping) : void{
 
  348    public function getHandler() : ?PacketHandler{
 
  349        return $this->handler;
 
  352    public function setHandler(?PacketHandler $handler) : void{
 
  353        if($this->connected){ 
 
  354            $this->handler = $handler;
 
  355            if($this->handler !== 
null){
 
  356                $this->handler->setUp();
 
  361    private function checkRepeatedPacketFilter(
string $buffer) : bool{
 
  362        if($buffer === $this->noisyPacketBuffer){
 
  363            $this->noisyPacketsDropped++;
 
  369        $this->noisyPacketBuffer = 
"";
 
  370        $this->noisyPacketsDropped = 0;
 
  379        if(!$this->connected){
 
  383        Timings::$playerNetworkReceive->startTiming();
 
  385            $this->packetBatchLimiter->decrement();
 
  387            if($this->cipher !== 
null){
 
  388                Timings::$playerNetworkReceiveDecrypt->startTiming();
 
  390                    $payload = $this->cipher->decrypt($payload);
 
  391                }
catch(DecryptionException $e){
 
  392                    $this->logger->debug(
"Encrypted packet: " . base64_encode($payload));
 
  393                    throw PacketHandlingException::wrap($e, 
"Packet decryption error");
 
  395                    Timings::$playerNetworkReceiveDecrypt->stopTiming();
 
  399            if(strlen($payload) < 1){
 
  400                throw new PacketHandlingException(
"No bytes in payload");
 
  403            if($this->enableCompression){
 
  404                $compressionType = ord($payload[0]);
 
  405                $compressed = substr($payload, 1);
 
  406                if($compressionType === CompressionAlgorithm::NONE){
 
  407                    $decompressed = $compressed;
 
  408                }elseif($compressionType === $this->compressor->getNetworkId()){
 
  409                    Timings::$playerNetworkReceiveDecompress->startTiming();
 
  411                        $decompressed = $this->compressor->decompress($compressed);
 
  412                    }
catch(DecompressionException $e){
 
  413                        $this->logger->debug(
"Failed to decompress packet: " . base64_encode($compressed));
 
  414                        throw PacketHandlingException::wrap($e, 
"Compressed packet batch decode error");
 
  416                        Timings::$playerNetworkReceiveDecompress->stopTiming();
 
  419                    throw new PacketHandlingException(
"Packet compressed with unexpected compression type $compressionType");
 
  422                $decompressed = $payload;
 
  427                $stream = 
new ByteBufferReader($decompressed);
 
  428                foreach(PacketBatch::decodeRaw($stream) as $buffer){
 
  429                    if(++$count >= self::INCOMING_PACKET_BATCH_HARD_LIMIT){
 
  433                        throw new PacketHandlingException(
"Reached hard limit of " . self::INCOMING_PACKET_BATCH_HARD_LIMIT . 
" per batch packet");
 
  436                    if($this->checkRepeatedPacketFilter($buffer)){
 
  440                    $this->gamePacketLimiter->decrement();
 
  441                    $packet = $this->packetPool->getPacket($buffer);
 
  442                    if($packet === 
null){
 
  443                        $this->logger->debug(
"Unknown packet: " . base64_encode($buffer));
 
  444                        throw new PacketHandlingException(
"Unknown packet received");
 
  447                        $this->handleDataPacket($packet, $buffer);
 
  448                    }
catch(PacketHandlingException $e){
 
  449                        $this->logger->debug($packet->getName() . 
": " . base64_encode($buffer));
 
  450                        throw PacketHandlingException::wrap($e, 
"Error processing " . $packet->getName());
 
  451                    }
catch(FilterNoisyPacketException){
 
  452                        $this->noisyPacketBuffer = $buffer;
 
  454                    if(!$this->isConnected()){
 
  456                        $this->logger->debug(
"Aborting batch processing due to server-side disconnection");
 
  460            }
catch(PacketDecodeException|DataDecodeException $e){
 
  461                $this->logger->logException($e);
 
  462                throw PacketHandlingException::wrap($e, 
"Packet batch decode error");
 
  465            Timings::$playerNetworkReceive->stopTiming();
 
 
  478        $timings = Timings::getReceiveDataPacketTimings($packet);
 
  479        $timings->startTiming();
 
  482            if(DataPacketDecodeEvent::hasHandlers()){
 
  485                if($ev->isCancelled()){
 
  490            $decodeTimings = Timings::getDecodeDataPacketTimings($packet);
 
  491            $decodeTimings->startTiming();
 
  493                $stream = 
new ByteBufferReader($buffer);
 
  495                    $packet->decode($stream);
 
  496                }
catch(PacketDecodeException $e){
 
  497                    throw PacketHandlingException::wrap($e);
 
  499                if($stream->getUnreadLength() > 0){
 
  500                    $remains = substr($stream->getData(), $stream->getOffset());
 
  501                    $this->logger->debug(
"Still " . strlen($remains) . 
" bytes unread in " . $packet->getName() . 
": " . bin2hex($remains));
 
  504                $decodeTimings->stopTiming();
 
  507            if(DataPacketReceiveEvent::hasHandlers()){
 
  508                $ev = 
new DataPacketReceiveEvent($this, $packet);
 
  510                if($ev->isCancelled()){
 
  514            $handlerTimings = Timings::getHandleDataPacketTimings($packet);
 
  515            $handlerTimings->startTiming();
 
  517                if($this->handler === 
null || !$packet->handle($this->handler)){
 
  518                    $this->logger->debug(
"Unhandled " . $packet->getName() . 
": " . base64_encode($stream->getData()));
 
  521                $handlerTimings->stopTiming();
 
  524            $timings->stopTiming();
 
 
  528    public function handleAckReceipt(
int $receiptId) : void{
 
  529        if(!$this->connected){
 
  532        if(isset($this->ackPromisesByReceiptId[$receiptId])){
 
  533            $promises = $this->ackPromisesByReceiptId[$receiptId];
 
  534            unset($this->ackPromisesByReceiptId[$receiptId]);
 
  535            foreach($promises as $promise){
 
  536                $promise->resolve(
true);
 
  544    private function sendDataPacketInternal(ClientboundPacket $packet, 
bool $immediate, ?PromiseResolver $ackReceiptResolver) : bool{
 
  545        if(!$this->connected){
 
  549        if(!$this->loggedIn && !$packet->canBeSentBeforeLogin()){
 
  550            throw new \InvalidArgumentException(
"Attempted to send " . get_class($packet) . 
" to " . $this->getDisplayName() . 
" too early");
 
  553        $timings = Timings::getSendDataPacketTimings($packet);
 
  554        $timings->startTiming();
 
  556            if(DataPacketSendEvent::hasHandlers()){
 
  557                $ev = new DataPacketSendEvent([$this], [$packet]);
 
  559                if($ev->isCancelled()){
 
  562                $packets = $ev->getPackets();
 
  564                $packets = [$packet];
 
  567            if($ackReceiptResolver !== 
null){
 
  568                $this->sendBufferAckPromises[] = $ackReceiptResolver;
 
  570            $writer = 
new ByteBufferWriter();
 
  571            foreach($packets as $evPacket){
 
  573                $this->addToSendBuffer(self::encodePacketTimed($writer, $evPacket));
 
  576                $this->flushGamePacketQueue();
 
  581            $timings->stopTiming();
 
  585    public function sendDataPacket(ClientboundPacket $packet, 
bool $immediate = 
false) : bool{
 
  586        return $this->sendDataPacketInternal($packet, $immediate, null);
 
  596        if(!$this->sendDataPacketInternal($packet, $immediate, $resolver)){
 
 
  606    public static function encodePacketTimed(ByteBufferWriter $serializer, ClientboundPacket $packet) : string{
 
  607        $timings = 
Timings::getEncodeDataPacketTimings($packet);
 
  608        $timings->startTiming();
 
  610            $packet->encode($serializer);
 
  611            return $serializer->getData();
 
  613            $timings->stopTiming();
 
  620    public function addToSendBuffer(
string $buffer) : void{
 
  621        $this->sendBuffer[] = $buffer;
 
  624    private function flushGamePacketQueue() : void{
 
  625        if(count($this->sendBuffer) > 0){
 
  626            Timings::$playerNetworkSend->startTiming();
 
  629                if($this->forceAsyncCompression){
 
  633                $stream = 
new ByteBufferWriter();
 
  634                PacketBatch::encodeRaw($stream, $this->sendBuffer);
 
  636                if($this->enableCompression){
 
  637                    $batch = $this->
server->prepareBatch($stream->getData(), $this->compressor, $syncMode, Timings::$playerNetworkSendCompressSessionBuffer);
 
  639                    $batch = $stream->getData();
 
  641                $this->sendBuffer = [];
 
  642                $ackPromises = $this->sendBufferAckPromises;
 
  643                $this->sendBufferAckPromises = [];
 
  646                $this->queueCompressedNoGamePacketFlush($batch, networkFlush: 
true, ackPromises: $ackPromises);
 
  648                Timings::$playerNetworkSend->stopTiming();
 
  653    public function getBroadcaster() : PacketBroadcaster{ return $this->broadcaster; }
 
  655    public function getEntityEventBroadcaster() : EntityEventBroadcaster{ return $this->entityEventBroadcaster; }
 
  657    public function getCompressor() : Compressor{
 
  658        return $this->compressor;
 
  661    public function getTypeConverter() : TypeConverter{ return $this->typeConverter; }
 
  663    public function queueCompressed(CompressBatchPromise|
string $payload, 
bool $immediate = 
false) : void{
 
  664        Timings::$playerNetworkSend->startTiming();
 
  668            $this->flushGamePacketQueue();
 
  669            $this->queueCompressedNoGamePacketFlush($payload, $immediate);
 
  671            Timings::$playerNetworkSend->stopTiming();
 
  680    private function queueCompressedNoGamePacketFlush(CompressBatchPromise|
string $batch, 
bool $networkFlush = 
false, array $ackPromises = []) : void{
 
  681        Timings::$playerNetworkSend->startTiming();
 
  683            $this->compressedQueue->enqueue([$batch, $ackPromises, $networkFlush]);
 
  684            if(is_string($batch)){
 
  685                $this->flushCompressedQueue();
 
  687                $batch->onResolve(
function() : 
void{
 
  688                    if($this->connected){
 
  689                        $this->flushCompressedQueue();
 
  694            Timings::$playerNetworkSend->stopTiming();
 
  698    private function flushCompressedQueue() : void{
 
  699        Timings::$playerNetworkSend->startTiming();
 
  701            while(!$this->compressedQueue->isEmpty()){
 
  703                [$current, $ackPromises, $networkFlush] = $this->compressedQueue->bottom();
 
  704                if(is_string($current)){
 
  705                    $this->compressedQueue->dequeue();
 
  706                    $this->sendEncoded($current, $networkFlush, $ackPromises);
 
  708                }elseif($current->hasResult()){
 
  709                    $this->compressedQueue->dequeue();
 
  710                    $this->sendEncoded($current->getResult(), $networkFlush, $ackPromises);
 
  718            Timings::$playerNetworkSend->stopTiming();
 
  726    private function sendEncoded(
string $payload, 
bool $immediate, array $ackPromises) : void{
 
  727        if($this->cipher !== null){
 
  728            Timings::$playerNetworkSendEncrypt->startTiming();
 
  729            $payload = $this->cipher->encrypt($payload);
 
  730            Timings::$playerNetworkSendEncrypt->stopTiming();
 
  733        if(count($ackPromises) > 0){
 
  734            $ackReceiptId = $this->nextAckReceiptId++;
 
  735            $this->ackPromisesByReceiptId[$ackReceiptId] = $ackPromises;
 
  737            $ackReceiptId = 
null;
 
  739        $this->sender->send($payload, $immediate, $ackReceiptId);
 
  745    private function tryDisconnect(\Closure $func, Translatable|
string $reason) : void{
 
  746        if($this->connected && !$this->disconnectGuard){
 
  747            $this->disconnectGuard = 
true;
 
  749            $this->disconnectGuard = 
false;
 
  750            $this->flushGamePacketQueue();
 
  751            $this->sender->close(
"");
 
  752            foreach($this->disposeHooks as $callback){
 
  755            $this->disposeHooks->clear();
 
  756            $this->setHandler(
null);
 
  757            $this->connected = 
false;
 
  759            $ackPromisesByReceiptId = $this->ackPromisesByReceiptId;
 
  760            $this->ackPromisesByReceiptId = [];
 
  761            foreach($ackPromisesByReceiptId as $resolvers){
 
  762                foreach($resolvers as $resolver){
 
  766            $sendBufferAckPromises = $this->sendBufferAckPromises;
 
  767            $this->sendBufferAckPromises = [];
 
  768            foreach($sendBufferAckPromises as $resolver){
 
  772            $this->logger->info($this->
server->getLanguage()->translate(KnownTranslationFactory::pocketmine_network_session_close($reason)));
 
  780    private function dispose() : void{
 
  781        $this->invManager = null;
 
  784    private function sendDisconnectPacket(Translatable|
string $message) : void{
 
  785        if($message instanceof Translatable){
 
  786            $translated = $this->
server->getLanguage()->translate($message);
 
  788            $translated = $message;
 
  790        $this->sendDataPacket(DisconnectPacket::create(0, $translated, 
""));
 
  800        $this->tryDisconnect(function() use ($reason, $disconnectScreenMessage, $notify) : void{
 
  802                $this->sendDisconnectPacket($disconnectScreenMessage ?? $reason);
 
  804            if($this->player !== 
null){
 
  805                $this->player->onPostDisconnect($reason, 
null);
 
 
  810    public function disconnectWithError(Translatable|
string $reason, Translatable|
string|
null $disconnectScreenMessage = 
null) : void{
 
  811        $errorId = implode(
"-", str_split(bin2hex(random_bytes(6)), 4));
 
  814            reason: KnownTranslationFactory::pocketmine_disconnect_error($reason, $errorId)->prefix(TextFormat::RED),
 
  815            disconnectScreenMessage: KnownTranslationFactory::pocketmine_disconnect_error($disconnectScreenMessage ?? $reason, $errorId),
 
  819    public function disconnectIncompatibleProtocol(
int $protocolVersion) : void{
 
  820        $this->tryDisconnect(
 
  821            function() use ($protocolVersion) : void{
 
  822                $this->sendDataPacket(PlayStatusPacket::create($protocolVersion < ProtocolInfo::CURRENT_PROTOCOL ? PlayStatusPacket::LOGIN_FAILED_CLIENT : PlayStatusPacket::LOGIN_FAILED_SERVER), true);
 
  824            KnownTranslationFactory::pocketmine_disconnect_incompatibleProtocol((
string) $protocolVersion)
 
  833        $this->tryDisconnect(
function() use ($ip, $port, $reason) : 
void{
 
  834            $this->sendDataPacket(TransferPacket::create($ip, $port, 
false), 
true);
 
  835            if($this->player !== 
null){
 
  836                $this->player->onPostDisconnect($reason, 
null);
 
 
  845        $this->tryDisconnect(function() use ($disconnectScreenMessage) : void{
 
  846            $this->sendDisconnectPacket($disconnectScreenMessage);
 
 
  855        $this->tryDisconnect(function() use ($reason) : void{
 
  856            if($this->player !== null){
 
  857                $this->player->onPostDisconnect($reason, 
null);
 
 
  862    private function setAuthenticationStatus(
bool $authenticated, 
bool $authRequired, 
Translatable|
string|
null $error, ?
string $clientPubKey) : void{
 
  863        if(!$this->connected){
 
  867            if($authenticated && !($this->info instanceof XboxLivePlayerInfo)){
 
  868                $error = 
"Expected XUID but none found";
 
  869            }elseif($clientPubKey === 
null){
 
  870                $error = 
"Missing client public key"; 
 
  875            $this->disconnectWithError(
 
  876                reason: KnownTranslationFactory::pocketmine_disconnect_invalidSession($error),
 
  877                disconnectScreenMessage: KnownTranslationFactory::pocketmine_disconnect_error_authentication()
 
  883        $this->authenticated = $authenticated;
 
  885        if(!$this->authenticated){
 
  887                $this->disconnect(
"Not authenticated", KnownTranslationFactory::disconnectionScreen_notAuthenticated());
 
  890            if($this->info instanceof XboxLivePlayerInfo){
 
  891                $this->logger->warning(
"Discarding unexpected XUID for non-authenticated player");
 
  892                $this->info = $this->info->withoutXboxData();
 
  895        $this->logger->debug(
"Xbox Live authenticated: " . ($this->authenticated ? 
"YES" : 
"NO"));
 
  897        $checkXUID = $this->
server->getConfigGroup()->getPropertyBool(YmlServerProperties::PLAYER_VERIFY_XUID, 
true);
 
  898        $myXUID = $this->info instanceof XboxLivePlayerInfo ? $this->info->getXuid() : 
"";
 
  899        $kickForXUIDMismatch = 
function(
string $xuid) use ($checkXUID, $myXUID) : bool{
 
  900            if($checkXUID && $myXUID !== $xuid){
 
  901                $this->logger->debug(
"XUID mismatch: expected '$xuid', but got '$myXUID'");
 
  906                $this->disconnect(
"XUID does not match (possible impersonation attempt)");
 
  912        foreach($this->manager->getSessions() as $existingSession){
 
  913            if($existingSession === $this){
 
  916            $info = $existingSession->getPlayerInfo();
 
  917            if($info !== 
null && (strcasecmp($info->getUsername(), $this->info->getUsername()) === 0 || $info->getUuid()->equals($this->info->getUuid()))){
 
  918                if($kickForXUIDMismatch($info instanceof XboxLivePlayerInfo ? $info->getXuid() : 
"")){
 
  921                $ev = 
new PlayerDuplicateLoginEvent($this, $existingSession, KnownTranslationFactory::disconnectionScreen_loggedinOtherLocation(), 
null);
 
  923                if($ev->isCancelled()){
 
  924                    $this->disconnect($ev->getDisconnectReason(), $ev->getDisconnectScreenMessage());
 
  928                $existingSession->disconnect($ev->getDisconnectReason(), $ev->getDisconnectScreenMessage());
 
  934        $this->cachedOfflinePlayerData = $this->
server->getOfflinePlayerData($this->info->getUsername());
 
  936            $recordedXUID = $this->cachedOfflinePlayerData !== 
null ? $this->cachedOfflinePlayerData->getTag(Player::TAG_LAST_KNOWN_XUID) : 
null;
 
  937            if(!($recordedXUID instanceof StringTag)){
 
  938                $this->logger->debug(
"No previous XUID recorded, no choice but to trust this player");
 
  939            }elseif(!$kickForXUIDMismatch($recordedXUID->getValue())){
 
  940                $this->logger->debug(
"XUID match");
 
  944        if(EncryptionContext::$ENABLED){
 
  945            $this->
server->getAsyncPool()->submitTask(
new PrepareEncryptionTask($clientPubKey, 
function(
string $encryptionKey, 
string $handshakeJwt) : 
void{
 
  946                if(!$this->connected){
 
  949                $this->sendDataPacket(ServerToClientHandshakePacket::create($handshakeJwt), 
true); 
 
  951                $this->cipher = EncryptionContext::fakeGCM($encryptionKey);
 
  953                $this->setHandler(
new HandshakePacketHandler($this->onServerLoginSuccess(...)));
 
  954                $this->logger->debug(
"Enabled encryption");
 
  957            $this->onServerLoginSuccess();
 
  961    private function onServerLoginSuccess() : void{
 
  962        $this->loggedIn = true;
 
  964        $this->sendDataPacket(PlayStatusPacket::create(PlayStatusPacket::LOGIN_SUCCESS));
 
  966        $this->logger->debug(
"Initiating resource packs phase");
 
  968        $packManager = $this->
server->getResourcePackManager();
 
  969        $resourcePacks = $packManager->getResourceStack();
 
  971        foreach($resourcePacks as $resourcePack){
 
  972            $key = $packManager->getPackEncryptionKey($resourcePack->getPackId());
 
  974                $keys[$resourcePack->getPackId()] = $key;
 
  977        $event = 
new PlayerResourcePackOfferEvent($this->info, $resourcePacks, $keys, $packManager->resourcePacksRequired());
 
  979        $this->setHandler(
new ResourcePacksPacketHandler($this, $event->getResourcePacks(), $event->getEncryptionKeys(), $event->mustAccept(), 
function() : 
void{
 
  980            $this->createPlayer();
 
  984    private function beginSpawnSequence() : void{
 
  985        $this->setHandler(new PreSpawnPacketHandler($this->
server, $this->player, $this, $this->invManager));
 
  986        $this->player->setNoClientPredictions(); 
 
  988        $this->logger->debug(
"Waiting for chunk radius request");
 
  991    public function notifyTerrainReady() : void{
 
  992        $this->logger->debug(
"Sending spawn notification, waiting for spawn response");
 
  993        $this->sendDataPacket(PlayStatusPacket::create(PlayStatusPacket::PLAYER_SPAWN));
 
  994        $this->setHandler(
new SpawnResponsePacketHandler($this->onClientSpawnResponse(...)));
 
  997    private function onClientSpawnResponse() : void{
 
  998        $this->logger->debug(
"Received spawn response, entering in-game phase");
 
  999        $this->player->setNoClientPredictions(
false); 
 
 1000        $this->player->doFirstSpawn();
 
 1001        $this->forceAsyncCompression = 
false;
 
 1002        $this->setHandler(
new InGamePacketHandler($this->player, $this, $this->invManager));
 
 1005    public function onServerDeath(Translatable|
string $deathMessage) : void{
 
 1006        if($this->handler instanceof InGamePacketHandler){ 
 
 1007            $this->setHandler(
new DeathPacketHandler($this->player, $this, $this->invManager ?? 
throw new AssumptionFailedError(), $deathMessage));
 
 1011    public function onServerRespawn() : void{
 
 1012        $this->entityEventBroadcaster->syncAttributes([$this], $this->player, $this->player->getAttributeMap()->getAll());
 
 1013        $this->player->sendData(
null);
 
 1015        $this->syncAbilities($this->player);
 
 1016        $this->invManager->syncAll();
 
 1017        $this->setHandler(
new InGamePacketHandler($this->player, $this, $this->invManager));
 
 1020    public function syncMovement(Vector3 $pos, ?
float $yaw = 
null, ?
float $pitch = 
null, 
int $mode = MovePlayerPacket::MODE_NORMAL) : void{
 
 1021        if($this->player !== null){
 
 1022            $location = $this->player->getLocation();
 
 1023            $yaw = $yaw ?? $location->getYaw();
 
 1024            $pitch = $pitch ?? $location->getPitch();
 
 1026            $this->sendDataPacket(MovePlayerPacket::simple(
 
 1027                $this->player->getId(),
 
 1028                $this->player->getOffsetPosition($pos),
 
 1033                $this->player->onGround,
 
 1038            if($this->handler instanceof InGamePacketHandler){
 
 1039                $this->handler->forceMoveSync = 
true;
 
 1044    public function syncViewAreaRadius(
int $distance) : void{
 
 1045        $this->sendDataPacket(ChunkRadiusUpdatedPacket::create($distance));
 
 1048    public function syncViewAreaCenterPoint(Vector3 $newPos, 
int $viewDistance) : void{
 
 1049        $this->sendDataPacket(NetworkChunkPublisherUpdatePacket::create(BlockPosition::fromVector3($newPos), $viewDistance * 16, [])); 
 
 1052    public function syncPlayerSpawnPoint(Position $newSpawn) : void{
 
 1053        $newSpawnBlockPosition = BlockPosition::fromVector3($newSpawn);
 
 1055        $this->sendDataPacket(SetSpawnPositionPacket::playerSpawn($newSpawnBlockPosition, DimensionIds::OVERWORLD, $newSpawnBlockPosition));
 
 1058    public function syncWorldSpawnPoint(Position $newSpawn) : void{
 
 1059        $this->sendDataPacket(SetSpawnPositionPacket::worldSpawn(BlockPosition::fromVector3($newSpawn), DimensionIds::OVERWORLD));
 
 1062    public function syncGameMode(GameMode $mode, 
bool $isRollback = 
false) : void{
 
 1063        $this->sendDataPacket(SetPlayerGameTypePacket::create($this->typeConverter->coreGameModeToProtocol($mode)));
 
 1064        if($this->player !== 
null){
 
 1065            $this->syncAbilities($this->player);
 
 1066            $this->syncAdventureSettings(); 
 
 1068        if(!$isRollback && $this->invManager !== 
null){
 
 1069            $this->invManager->syncCreative();
 
 1073    public function syncAbilities(Player $for) : void{
 
 1074        $isOp = $for->hasPermission(DefaultPermissions::ROOT_OPERATOR);
 
 1078            AbilitiesLayer::ABILITY_ALLOW_FLIGHT => $for->getAllowFlight(),
 
 1079            AbilitiesLayer::ABILITY_FLYING => $for->isFlying(),
 
 1080            AbilitiesLayer::ABILITY_NO_CLIP => !$for->hasBlockCollision(),
 
 1081            AbilitiesLayer::ABILITY_OPERATOR => $isOp,
 
 1082            AbilitiesLayer::ABILITY_TELEPORT => $for->hasPermission(DefaultPermissionNames::COMMAND_TELEPORT_SELF),
 
 1083            AbilitiesLayer::ABILITY_INVULNERABLE => $for->isCreative(),
 
 1084            AbilitiesLayer::ABILITY_MUTED => 
false,
 
 1085            AbilitiesLayer::ABILITY_WORLD_BUILDER => 
false,
 
 1086            AbilitiesLayer::ABILITY_INFINITE_RESOURCES => !$for->hasFiniteResources(),
 
 1087            AbilitiesLayer::ABILITY_LIGHTNING => 
false,
 
 1088            AbilitiesLayer::ABILITY_BUILD => !$for->isSpectator(),
 
 1089            AbilitiesLayer::ABILITY_MINE => !$for->isSpectator(),
 
 1090            AbilitiesLayer::ABILITY_DOORS_AND_SWITCHES => !$for->isSpectator(),
 
 1091            AbilitiesLayer::ABILITY_OPEN_CONTAINERS => !$for->isSpectator(),
 
 1092            AbilitiesLayer::ABILITY_ATTACK_PLAYERS => !$for->isSpectator(),
 
 1093            AbilitiesLayer::ABILITY_ATTACK_MOBS => !$for->isSpectator(),
 
 1094            AbilitiesLayer::ABILITY_PRIVILEGED_BUILDER => 
false,
 
 1098            new AbilitiesLayer(AbilitiesLayer::LAYER_BASE, $boolAbilities, $for->getFlightSpeedMultiplier(), 1, 0.1),
 
 1100        if(!$for->hasBlockCollision()){
 
 1106            $layers[] = new AbilitiesLayer(AbilitiesLayer::LAYER_SPECTATOR, [
 
 1107                AbilitiesLayer::ABILITY_FLYING => true,
 
 1108            ], null, null, null);
 
 1111        $this->sendDataPacket(UpdateAbilitiesPacket::create(
new AbilitiesData(
 
 1112            $isOp ? CommandPermissions::OPERATOR : CommandPermissions::NORMAL,
 
 1113            $isOp ? PlayerPermissions::OPERATOR : PlayerPermissions::MEMBER,
 
 1119    public function syncAdventureSettings() : void{
 
 1120        if($this->player === null){
 
 1121            throw new \LogicException(
"Cannot sync adventure settings for a player that is not yet created");
 
 1124        $this->sendDataPacket(UpdateAdventureSettingsPacket::create(
 
 1125            noAttackingMobs: 
false,
 
 1126            noAttackingPlayers: 
false,
 
 1127            worldImmutable: 
false,
 
 1129            autoJump: $this->player->hasAutoJump()
 
 1133    public function syncAvailableCommands() : void{
 
 1135        foreach($this->
server->getCommandMap()->getCommands() as $command){
 
 1136            if(isset($commandData[$command->getLabel()]) || $command->getLabel() === 
"help" || !$command->testPermissionSilent($this->player)){
 
 1140            $lname = strtolower($command->getLabel());
 
 1141            $aliases = $command->getAliases();
 
 1143            if(count($aliases) > 0){
 
 1144                if(!in_array($lname, $aliases, 
true)){
 
 1146                    $aliases[] = $lname;
 
 1148                $aliasObj = 
new CommandHardEnum(ucfirst($command->getLabel()) . 
"Aliases", $aliases);
 
 1151            $description = $command->getDescription();
 
 1152            $data = 
new CommandData(
 
 1154                $description instanceof Translatable ? $this->player->getLanguage()->translate($description) : $description,
 
 1159                    new CommandOverload(chaining: 
false, parameters: [CommandParameter::standard(
"args", AvailableCommandsPacket::ARG_TYPE_RAWTEXT, 0, 
true)])
 
 1161                chainedSubCommandData: []
 
 1164            $commandData[$command->getLabel()] = $data;
 
 1167        $this->sendDataPacket(AvailableCommandsPacketAssembler::assemble(array_values($commandData), [], []));
 
 1176        $language = $this->player->getLanguage();
 
 1178        return [$language->translateString($message->getText(), $parameters, 
"pocketmine."), $parameters];
 
 
 1181    public function onChatMessage(
Translatable|
string $message) : void{
 
 1183            if(!$this->
server->isLanguageForced()){
 
 1184                $this->sendDataPacket(TextPacket::translation(...$this->prepareClientTranslatableMessage($message)));
 
 1186                $this->sendDataPacket(TextPacket::raw($this->player->getLanguage()->translate($message)));
 
 1189            $this->sendDataPacket(TextPacket::raw($message));
 
 1193    public function onJukeboxPopup(Translatable|
string $message) : void{
 
 1195        if($message instanceof Translatable){
 
 1196            if(!$this->server->isLanguageForced()){
 
 1197                [$message, $parameters] = $this->prepareClientTranslatableMessage($message);
 
 1199                $message = $this->player->getLanguage()->translate($message);
 
 1202        $this->sendDataPacket(TextPacket::jukeboxPopup($message, $parameters));
 
 1205    public function onPopup(
string $message) : void{
 
 1206        $this->sendDataPacket(TextPacket::popup($message));
 
 1209    public function onTip(
string $message) : void{
 
 1210        $this->sendDataPacket(TextPacket::tip($message));
 
 1213    public function onFormSent(
int $id, Form $form) : bool{
 
 1214        return $this->sendDataPacket(ModalFormRequestPacket::create($id, json_encode($form, JSON_THROW_ON_ERROR)));
 
 1217    public function onCloseAllForms() : void{
 
 1218        $this->sendDataPacket(ClientboundCloseFormPacket::create());
 
 1224    private function sendChunkPacket(
string $chunkPacket, \Closure $onCompletion, World $world) : void{
 
 1225        $world->timings->syncChunkSend->startTiming();
 
 1227            $this->queueCompressed($chunkPacket);
 
 1230            $world->timings->syncChunkSend->stopTiming();
 
 1240        $world = $this->player->getLocation()->getWorld();
 
 1241        $promiseOrPacket = ChunkCache::getInstance($world, $this->compressor)->request($chunkX, $chunkZ);
 
 1242        if(is_string($promiseOrPacket)){
 
 1243            $this->sendChunkPacket($promiseOrPacket, $onCompletion, $world);
 
 1246        $promiseOrPacket->onResolve(
 
 1249                if(!$this->isConnected()){
 
 1252                $currentWorld = $this->player->getLocation()->getWorld();
 
 1253                if($world !== $currentWorld || ($status = $this->player->getUsedChunkStatus($chunkX, $chunkZ)) === 
null){
 
 1254                    $this->logger->debug(
"Tried to send no-longer-active chunk $chunkX $chunkZ in world " . $world->getFolderName());
 
 1257                if($status !== UsedChunkStatus::REQUESTED_SENDING){
 
 1264                $this->sendChunkPacket($promise->getResult(), $onCompletion, $world);
 
 
 1269    public function stopUsingChunk(
int $chunkX, 
int $chunkZ) : void{
 
 1273    public function onEnterWorld() : void{
 
 1274        if($this->player !== null){
 
 1275            $world = $this->player->getWorld();
 
 1276            $this->syncWorldTime($world->getTime());
 
 1277            $this->syncWorldDifficulty($world->getDifficulty());
 
 1278            $this->syncWorldSpawnPoint($world->getSpawnLocation());
 
 1283    public function syncWorldTime(
int $worldTime) : void{
 
 1284        $this->sendDataPacket(SetTimePacket::create($worldTime));
 
 1287    public function syncWorldDifficulty(
int $worldDifficulty) : void{
 
 1288        $this->sendDataPacket(SetDifficultyPacket::create($worldDifficulty));
 
 1291    public function getInvManager() : ?InventoryManager{
 
 1292        return $this->invManager;
 
 1300            return 
PlayerListEntry::createAdditionEntry($player->getUniqueId(), $player->getId(), $player->getDisplayName(), $this->typeConverter->getSkinAdapter()->toSkinData($player->getSkin()), $player->getXuid());
 
 
 1304    public function onPlayerAdded(
Player $p) : void{
 
 1305        $this->sendDataPacket(PlayerListPacket::add([PlayerListEntry::createAdditionEntry($p->getUniqueId(), $p->getId(), $p->getDisplayName(), $this->typeConverter->getSkinAdapter()->toSkinData($p->getSkin()), $p->getXuid())]));
 
 1308    public function onPlayerRemoved(
Player $p) : void{
 
 1309        if($p !== $this->player){
 
 1310            $this->sendDataPacket(PlayerListPacket::remove([PlayerListEntry::createRemovalEntry($p->
getUniqueId())]));
 
 1314    public function onTitle(
string $title) : void{
 
 1315        $this->sendDataPacket(SetTitlePacket::title($title));
 
 1318    public function onSubTitle(
string $subtitle) : void{
 
 1319        $this->sendDataPacket(SetTitlePacket::subtitle($subtitle));
 
 1322    public function onActionBar(
string $actionBar) : void{
 
 1323        $this->sendDataPacket(SetTitlePacket::actionBarMessage($actionBar));
 
 1326    public function onClearTitle() : void{
 
 1327        $this->sendDataPacket(SetTitlePacket::clearTitle());
 
 1330    public function onResetTitleOptions() : void{
 
 1331        $this->sendDataPacket(SetTitlePacket::resetTitleOptions());
 
 1334    public function onTitleDuration(
int $fadeIn, 
int $stay, 
int $fadeOut) : void{
 
 1335        $this->sendDataPacket(SetTitlePacket::setAnimationTimes($fadeIn, $stay, $fadeOut));
 
 1338    public function onToastNotification(
string $title, 
string $body) : void{
 
 1339        $this->sendDataPacket(ToastRequestPacket::create($title, $body));
 
 1342    public function onOpenSignEditor(Vector3 $signPosition, 
bool $frontSide) : void{
 
 1343        $this->sendDataPacket(OpenSignPacket::create(BlockPosition::fromVector3($signPosition), $frontSide));
 
 1346    public function onItemCooldownChanged(Item $item, 
int $ticks) : void{
 
 1347        $this->sendDataPacket(PlayerStartItemCooldownPacket::create(
 
 1348            GlobalItemDataHandlers::getSerializer()->serializeType($item)->getName(),
 
 1353    public function tick() : void{
 
 1354        if(!$this->isConnected()){
 
 1359        if($this->info === 
null){
 
 1360            if(time() >= $this->connectTime + 10){
 
 1361                $this->disconnectWithError(KnownTranslationFactory::pocketmine_disconnect_error_loginTimeout());
 
 1367        if($this->player !== 
null){
 
 1368            $this->player->doChunkRequests();
 
 1370            $dirtyAttributes = $this->player->getAttributeMap()->needSend();
 
 1371            $this->entityEventBroadcaster->syncAttributes([$this], $this->player, $dirtyAttributes);
 
 1372            foreach($dirtyAttributes as $attribute){
 
 1375                $attribute->markSynchronized();
 
 1378        Timings::$playerNetworkSendInventorySync->startTiming();
 
 1380            $this->invManager?->flushPendingUpdates();
 
 1382            Timings::$playerNetworkSendInventorySync->stopTiming();
 
 1385        $this->flushGamePacketQueue();