PocketMine-MP Flicker-Free Red Scoreboard Plugin

Answered using AI by Vibe Coding

Code Question:

Напиши код плагина для PocketMine-MP PM API 5.37.3 на PHP отправка всем игрокам на сервере постоянного табло ScoreBoard с 15 строками с красными цифрами от 0 до 14 начиная снизу, и чтобы он не мерцал у игроков при отображении, и чтобы работал как надо без ошибок!!!

Code Solution:

Отлично! Вот код плагина для PocketMine-MP PM API 5.37.3, который отправит всем игрокам постоянное табло Scoreboard с 15 строками, содержащими красные цифры от 0 до 14, начиная снизу, и будет работать без мерцания.

Основные принципы для избежания мерцания:

  1. Отправка полного табло только один раз: При первом подключении игрока или загрузке плагина, мы отправляем весь Scoreboard.
  2. Обновление только измененных строк: Если какая-то строка должна измениться (в данном случае, она не будет), мы бы отправляли только изменение этой конкретной строки, а не всего табло. В этом примере строки статичны, поэтому после первой отправки они не будут обновляться.
  3. Правильное использование NetworkBinaryStream: Для создания пакетов Scoreboard'а.

Файл: ScoreboardPlugin.php

<?php

namespace MyPlugins\ScoreboardPlugin;

use pocketmine\plugin\PluginBase;
use pocketmine\event\Listener;
use pocketmine\event\player\PlayerJoinEvent;
use pocketmine\network\mcpe\protocol\SetDisplayObjectivePacket;
use pocketmine\network\mcpe\protocol\SetScorePacket;
use pocketmine\network\mcpe\protocol\RemoveObjectivePacket;
use pocketmine\network\mcpe\protocol\types\ScorePacketEntry;
use pocketmine\player\Player;
use pocketmine\utils\TextFormat;

class ScoreboardPlugin extends PluginBase implements Listener {

    private const OBJECTIVE_NAME = "my_scoreboard";
    private const DISPLAY_SLOT = "sidebar";
    private const DISPLAY_NAME = TextFormat::BOLD . TextFormat::YELLOW . "Мой Scoreboard"; // Заголовок Scoreboard'а

    public function onEnable(): void {
        $this->getServer()->getPluginManager()->registerEvents($this, $this);
        $this->getLogger()->info("ScoreboardPlugin enabled!");

        // Отправляем Scoreboard всем игрокам, которые уже на сервере
        foreach ($this->getServer()->getOnlinePlayers() as $player) {
            $this->sendScoreboard($player);
        }
    }

    public function onDisable(): void {
        $this->getLogger()->info("ScoreboardPlugin disabled!");

        // Удаляем Scoreboard у всех игроков при выключении плагина
        foreach ($this->getServer()->getOnlinePlayers() as $player) {
            $this->removeScoreboard($player);
        }
    }

    /**
     * Отправляет Scoreboard игроку при его присоединении.
     * @param PlayerJoinEvent $event
     */
    public function onPlayerJoin(PlayerJoinEvent $event): void {
        $player = $event->getPlayer();
        $this->sendScoreboard($player);
    }

    /**
     * Отправляет постоянный Scoreboard игроку.
     * @param Player $player
     */
    private function sendScoreboard(Player $player): void {
        // Сначала удаляем существующий Scoreboard, чтобы избежать дублирования или проблем
        $this->removeScoreboard($player);

        // 1. Создаем и отправляем пакет для отображения Objective (цели) Scoreboard'а
        $pk = new SetDisplayObjectivePacket();
        $pk->displaySlot = self::DISPLAY_SLOT;
        $pk->objectiveName = self::OBJECTIVE_NAME;
        $pk->displayName = self::DISPLAY_NAME;
        $pk->criteriaName = "dummy"; // Неизменяемый критерий
        $pk->sortOrder = SetDisplayObjectivePacket::SORT_ORDER_DESCENDING; // Сортировка по убыванию (14 сверху, 0 снизу)
        $player->getNetworkSession()->sendDataPacket($pk);

        // 2. Создаем и отправляем пакет со всеми записями Scoreboard'а
        $scorePacket = new SetScorePacket();
        $scorePacket->type = SetScorePacket::TYPE_CHANGE;
        $entries = [];

        // Создаем 15 строк с красными цифрами от 0 до 14, начиная снизу
        // Чтобы 0 был снизу, а 14 сверху, мы будем использовать score от 0 до 14
        // и TextFormat::RED для цвета
        for ($i = 0; $i < 15; $i++) {
            $entry = new ScorePacketEntry();
            $entry->objectiveName = self::OBJECTIVE_NAME;
            $entry->score = $i; // Score определяет порядок, чем меньше score, тем ниже строка (при SORT_ORDER_DESCENDING)
            $entry->scoreboardId = $i; // Уникальный ID для каждой записи
            $entry->type = ScorePacketEntry::TYPE_FAKE_PLAYER; // Используем тип "фейковый игрок" для произвольного текста
            $entry->customName = TextFormat::RED . $i; // Красная цифра
            $entries[] = $entry;
        }
        $scorePacket->entries = $entries;
        $player->getNetworkSession()->sendDataPacket($scorePacket);
    }

    /**
     * Удаляет Scoreboard у игрока.
     * @param Player $player
     */
    private function removeScoreboard(Player $player): void {
        $pk = new RemoveObjectivePacket();
        $pk->objectiveName = self::OBJECTIVE_NAME;
        $player->getNetworkSession()->sendDataPacket($pk);
    }
}

Как это работает и почему не мерцает:

  1. SetDisplayObjectivePacket: Этот пакет определяет, что Scoreboard существует, его имя (objectiveName), заголовок (displayName) и позицию (displaySlot - в данном случае, sidebar). Он отправляется только один раз, когда Scoreboard инициализируется для игрока.
  2. SetScorePacket с TYPE_CHANGE:
    • Когда мы впервые отправляем Scoreboard, мы создаем пакет SetScorePacket с типом TYPE_CHANGE и заполняем его всеми 15 записями (ScorePacketEntry). Каждая запись содержит уникальный scoreboardId, objectiveName, score (который определяет порядок отображения) и customName (текст строки).
    • Важно для отсутствия мерцания: Поскольку содержимое строк статично (красные цифры от 0 до 14), после первой отправки SetScorePacket эти строки остаются на месте. Мы не отправляем его снова и снова в цикле, что обычно вызывает мерцание. Если бы строки менялись, мы бы отправляли SetScorePacket только для тех строк, которые изменились, а не для всего табло.
  3. RemoveObjectivePacket: Используется для полного удаления Scoreboard'а. Мы используем его перед отправкой нового Scoreboard, чтобы убедиться, что старый Scoreboard полностью очищен, прежде чем мы отобразим новый. Это помогает избежать возможных артефактов или наложений, если Scoreboard был ранее отображен.
  4. ScorePacketEntry::score и SetDisplayObjectivePacket::sortOrder:
    • score в ScorePacketEntry определяет порядок строк. Чем выше score, тем выше строка.
    • sortOrder в SetDisplayObjectivePacket определяет, как сортируются записи. SORT_ORDER_DESCENDING означает, что записи с большим score будут выше.
    • В нашем случае, чтобы 0 был снизу, а 14 сверху, мы присваиваем score от 0 до 14. Сортировка по убыванию (DESCENDING) означает, что запись со score=14 будет отображаться выше, чем запись со score=0.
  5. TextFormat::RED: Используется для окрашивания цифр в красный цвет.
  6. ScorePacketEntry::TYPE_FAKE_PLAYER: Этот тип записи позволяет нам отображать произвольный текст (в customName) вместо имени реального игрока.

Установка и запуск:

  1. Создайте папку ScoreboardPlugin в директории plugins вашего сервера PocketMine-MP.

  2. Сохраните код выше в файле ScoreboardPlugin.php внутри этой папки.

  3. Создайте файл plugin.yml в папке ScoreboardPlugin со следующим содержимым:

    name: ScoreboardPlugin
    main: MyPlugins\ScoreboardPlugin\ScoreboardPlugin
    version: 1.0.0
    api: [5.0.0] # Убедитесь, что API соответствует вашей версии PocketMine-MP
    author: YourName
    description: Displays a permanent scoreboard with red numbers.
    
    • Важно: Убедитесь, что api: [5.0.0] соответствует вашей версии PocketMine-MP (поскольку вы указали 5.37.3, это будет работать).
  4. Перезапустите или загрузите плагин на вашем сервере PocketMine-MP.

Теперь, когда игроки присоединятся к вашему серверу, они увидят постоянное табло Scoreboard с красными цифрами от 0 до 14, расположен