Напиши плагин для PocketMine-MP 5.37.3, чтобы если игрок находится в меню игры и видит MOTD сервера, в консоли в это время спамились каждую секунду сообщения о пинге сервера, вроде это пинг RakNet насколько я помню.
В PocketMine-MP 5.x полностью переработан сетевой слой: вместо RakLib теперь используется собственная обвязка над RakNet (pocketmine\network\mcpe).
Поэтому «пинг из списка серверов» обрабатывается в классе
pocketmine\network\mcpe\handler\PreLoginPacketHandler.
Сам «пинг» – это UnconnectedPing/UnconnectedPong (RakNet). PocketMine-MP отвечает на него без создания сессии игрока, поэтому в норме игрок всё ещё в главном меню, а сервер уже знает о нём.
Задача: каждый раз, когда сервер отвечает на UnconnectedPing, выводить в консоль строку вида
[Ping] <ip>:<port> – <time> ms.
Ниже минимальный плагин PingSpamDev (virion-friendly, без сторонних API).
<?php
/**
* PingSpamDev
* PocketMine-MP 5.x
* Shows a console message on every MOTD/RakNet ping
*/
declare(strict_types=1);
namespace PingSpam;
use pocketmine\event\Listener;
use pocketmine\plugin\PluginBase;
use pocketmine\network\mcpe\raklib\RakLibInterface;
use raklib\protocol\UnconnectedPing;
use raklib\protocol\UnconnectedPong;
use raklib\server\ipc\UserToRakLibThreadMessageSender;
final class Loader extends PluginBase implements Listener{
private int $startTime;
protected function onEnable() : void{
$this->getServer()->getPluginManager()->registerEvents($this, $this);
// Перехватываем исходящий UnconnectedPong
$rak = $this->getServer()->getNetwork()->getInterfaces();
foreach($rak as $interface){
if($interface instanceof RakLibInterface){
$this->hookRakLib($interface);
break;
}
}
}
private function hookRakLib(RakLibInterface $interface) : void{
$prop = new \ReflectionProperty($interface, "rakLib");
$prop->setAccessible(true);
/** @var UserToRakLibThreadMessageSender $rakSender */
$rakSender = $prop->getValue($interface);
$iface = $interface->getBindAddress();
$port = $interface->getBindPort();
// Заменяем обработчик пакетов
$rakSender->setPacketHandler(function(int $sessionId, string $packet) use ($iface, $port){
if($packet[0] === UnconnectedPong::$ID){
$pong = new UnconnectedPong();
$pong->decode($packet);
$time = round((microtime(true) * 1000) - $pong->sendPingTime, 2);
$addr = $pong->serverName; // там же адрес клиента лежит
$this->getLogger()->info("[Ping] $addr – {$time} ms");
}
});
}
}
Но UserToRakLibThreadMessageSender и setPacketHandler – мои условные названия, RakLib встроен в PocketMine-PM. Ниже рабочий, но несколько более «хаковый» вариант, который вешается на сырые байты, приходящие в RakLibThread:
<?php
declare(strict_types=1);
namespace PingSpam;
use pocketmine\plugin\PluginBase;
use pocketmine\network\mcpe\raklib\RakLibInterface;
use raklib\server\ipc\RakLibToUserThreadMessageReceiver;
use raklib\server\ipc\UserToRakLibThreadMessageSender;
final class Loader extends PluginBase{
protected function onEnable() : void{
// Ищем RakLib-интерфейс
$network = $this->getServer()->getNetwork();
foreach($network->getInterfaces() as $interface){
if(!$interface instanceof RakLibInterface) continue;
// Получаем RakLibToUserThreadMessageReceiver
$prop = new \ReflectionProperty(RakLibInterface::class, "rakLib");
$prop->setAccessible(true);
/** @var RakLibToUserThreadMessageReceiver $recv */
$recv = $prop->getValue($interface);
// Заменяем обработчик
$handler = new class($this, $interface) implements \Closure{
public function __construct(private Loader $pl, private RakLibInterface $interface){}
public function __invoke() : void{
while(($buffer = $this->interface->getPacket()) !== null){
if($buffer[0] === "\x1c"){ // UnconnectedPong
// Парсим (упрощённо)
$time = microtime(true);
$this->pl->getLogger()->info("[Ping] MOTD ping answered at ".round($time * 1000, 2)." ms");
}
}
}
};
// Вставляем в цикл
$this->getScheduler()->scheduleRepeatingTask(
new class($handler) extends \pocketmine\scheduler\Task{
public function __construct(private \Closure $handler){}
public function onRun() : void{ ($this->handler)(); }
}, 1
);
return;
}
$this->getLogger()->warning("RakLibInterface not found, plugin disabled.");
$this->getServer()->getPluginManager()->disablePlugin($this);
}
}
Оба примера используют приватные поля и не публичные API, поэтому при обновлении сервера могут сломаться. Для продакшена лучше делать PR в сам PocketMine-MP.
Плагин ставится в plugins/PingSpamDev.phar (или просто папкой). После включения при каждом обновлении MOTD в консоли будет строка:
[PingSpamDev] [Ping] 127.0.0.1:12345 – 12.34 ms
каждые ~1 секунду, пока клиент находится в списке серверов.