PocketMine-MP BossBar & Scoreboard Plugin with Configurable UI

Answered using AI by Vibe Coding

Code Question:

Напиши рабочий код для PocketMine-MP с config.yml в котором можно будет отображать всем игрокам на сервере кастомный текст в BossBar (с возможностью смены цвета полоски BLUE, GREEN, PINK, PURPLE, RED, WHITE, YELLOW, REBECCA_PURPLE), ScoreBoard (красные цифры cound (очки) от 0 до 14 снизу-вверх отображаются) первая строка cound 0, при добавлении других по очереди идут 1, 2, 3 в config.yml а так же возможность установить Title (displayName) для этого ScoreBoard, Title и SubTitle (вызываются командой из config.yml), ActionBar и SendTip отображаются постоянно каждую секунду с возможностью использовать \n для новых линий и offset для перемещения по экрану всего текста слева-направо, а также модификация строки опыта Experience уровень и прогресс, ну и поддержка создавать анимации как в плагине TAB. Напиши пожалуйста рабочий коду моляю.

Code Solution:
<?php

declare(strict_types=1);

namespace BossScore;

use pocketmine\plugin\PluginBase;
use pocketmine\event\Listener;
use pocketmine\event\player\PlayerJoinEvent;
use pocketmine\scheduler\ClosureTask;
use pocketmine\command\Command;
use pocketmine\command\CommandSender;
use pocketmine\player\Player;
use pocketmine\entity\Attribute;
use pocketmine\network\mcpe\protocol\BossEventPacket;
use pocketmine\network\mcpe\protocol\SetDisplayObjectivePacket;
use pocketmine\network\mcpe\protocol\SetScorePacket;
use pocketmine\network\mcpe\protocol\SetTitlePacket;
use pocketmine\network\mcpe\protocol\types\ScorePacketEntry;
use pocketmine\network\mcpe\protocol\types\BossBarColor;
use pocketmine\math\Vector2;

class Main extends PluginBase implements Listener {

    private array $bossBarIds = [];
    private array $scoreboards = [];
    private array $titles = [];

    public function onEnable(): void {
        $this->saveDefaultConfig();
        $this->getServer()->getPluginManager()->registerEvents($this, $this);

        $period = $this->getConfig()->get("update-period", 20);
        $this->getScheduler()->scheduleRepeatingTask(new ClosureTask(function () {
            foreach ($this->getServer()->getOnlinePlayers() as $p) {
                $this->updateBossBar($p);
                $this->updateScoreboard($p);
                $this->updateActionBar($p);
                $this->updateExperience($p);
            }
        }), $period);
    }

    /* --------- BossBar --------- */
    private function updateBossBar(Player $p): void {
        $cfg = $this->getConfig();
        $text = $this->replacePlaceholders($cfg->get("bossbar-text", "§6Hello!"), $p);
        $color = $this->getColorFromString($cfg->get("bossbar-color", "RED"));

        if (!isset($this->bossBarIds[$p->getId()])) {
            $this->bossBarIds[$p->getId()] = $eid = $this->getNextEid();
            $p->getNetworkSession()->sendDataPacket(BossEventPacket::show($eid, $text, 1.0, $color));
        } else {
            $eid = $this->bossBarIds[$p->getId()];
            $p->getNetworkSession()->sendDataPacket(BossEventPacket::title($eid, $text));
            $p->getNetworkSession()->sendDataPacket(BossEventPacket::color($eid, $color));
        }
    }

    private function getColorFromString(string $color): int {
        return match (strtoupper($color)) {
            "BLUE" => BossBarColor::BLUE,
            "GREEN" => BossBarColor::GREEN,
            "PINK" => BossBarColor::PINK,
            "PURPLE" => BossBarColor::PURPLE,
            "RED" => BossBarColor::RED,
            "WHITE" => BossBarColor::WHITE,
            "YELLOW" => BossBarColor::YELLOW,
            "REBECCA_PURPLE" => 8, // custom
            default => BossBarColor::RED,
        };
    }

    /* --------- ScoreBoard --------- */
    private function updateScoreboard(Player $p): void {
        $cfg = $this->getConfig();
        $title = $this->replacePlaceholders($cfg->get("scoreboard-title", "§l§cINFO"), $p);

        if (!isset($this->scoreboards[$p->getId()])) {
            $pk = SetDisplayObjectivePacket::create(
                SetDisplayObjectivePacket::DISPLAY_SLOT_SIDEBAR,
                "objective",
                $title,
                "dummy",
                SetDisplayObjectivePacket::SORT_ORDER_ASCENDING
            );
            $p->getNetworkSession()->sendDataPacket($pk);
            $this->scoreboards[$p->getId()] = true;
        }

        $lines = [];
        foreach ($cfg->get("scoreboard-lines", []) as $i => $line) {
            if ($i > 14) break;
            $lines[] = $this->replacePlaceholders($line, $p);
        }

        // Remove old lines
        $pk = new SetScorePacket();
        $pk->type = SetScorePacket::TYPE_REMOVE;
        $entries = [];
        foreach (range(0, 14) as $score) {
            $entry = new ScorePacketEntry();
            $entry->objectiveName = "objective";
            $entry->score = $score;
            $entry->scoreboardId = $score;
            $entries[] = $entry;
        }
        $pk->entries = $entries;
        $p->getNetworkSession()->sendDataPacket($pk);

        // Add new lines
        $pk = new SetScorePacket();
        $pk->type = SetScorePacket::TYPE_CHANGE;
        $entries = [];
        foreach ($lines as $i => $text) {
            $entry = new ScorePacketEntry();
            $entry->objectiveName = "objective";
            $entry->score = $i;
            $entry->scoreboardId = $i;
            $entry->type = ScorePacketEntry::TYPE_FAKE_PLAYER;
            $entry->customName = $text;
            $entries[] = $entry;
        }
        $pk->entries = $entries;
        $p->getNetworkSession()->sendDataPacket($pk);
    }

    /* --------- ActionBar / Tip --------- */
    private function updateActionBar(Player $p): void {
        $cfg = $this->getConfig();
        $text = $this->replacePlaceholders($cfg->get("actionbar-text", "§aActionBar"), $p);
        $offset = (int) $cfg->get("actionbar-offset", 0);
        $tip = $this->replacePlaceholders($cfg->get("tip-text", "§bTip"), $p);

        $text = str_repeat(" ", $offset) . str_replace("\\n", "\n", $text);
        $p->sendActionBarMessage($text);
        $p->sendTip($tip);
    }

    /* --------- Experience --------- */
    private function updateExperience(Player $p): void {
        $cfg = $this->getConfig();
        $level = (int) $this->replacePlaceholders($cfg->get("exp-level", "0"), $p);
        $progress = (float) $this->replacePlaceholders($cfg->get("exp-progress", "0.5"), $p);

        $p->getXpManager()->setXpLevel($level);
        $p->getXpManager()->setXpProgress($progress);
    }

    /* --------- Commands --------- */
    public function onCommand(CommandSender $s, Command $c, string $label, array $args): bool {
        $cmd = strtolower($c->getName());
        $cfg = $this->getConfig();

        if ($cmd === $cfg->get("title-command", "mytitle")) {
            if (!$s instanceof Player) return false;
            $title = $cfg->get("title-text", "§eTitle");
            $subtitle = $cfg->get("subtitle-text", "§7Subtitle");
            $fadeIn = (int) $cfg->get("title-fadein", 10);
            $stay = (int) $cfg->get("title-stay", 70);
            $fadeOut = (int) $cfg->get("title-fadeout", 20);

            $pk = SetTitlePacket::title($this->replacePlaceholders($title, $s), $fadeIn, $stay, $fadeOut);
            $s->getNetworkSession()->sendDataPacket($pk);

            $pk = SetTitlePacket::subtitle($this->replacePlaceholders($subtitle, $s), $fadeIn, $stay, $fadeOut);
            $s->getNetworkSession()->sendDataPacket($pk);
            return true;
        }

        return false;
    }

    /* --------- Helpers --------- */
    private function replacePlaceholders(string $text, Player $p): string {
        return str_replace(
            ["{player}", "{online}", "{max_online}"],
            [$p->getName(), count($this->getServer()->getOnlinePlayers()), $this->getServer()->getMaxPlayers()],
            $text
        );
    }

    private function getNextEid(): int {
        static $eid = 1000;
        return ++$eid;
    }

    public function onPlayerJoin(PlayerJoinEvent $e): void {
        unset($this->bossBarIds[$e->getPlayer()->getId()]);
    }
}

plugin.yml

name: BossScore
main: BossScore\Main
version: 1.0
api: 5.0.0
commands:
  mytitle:
    description: "Показать Title"
    usage: /mytitle

resources/config.yml

# Период обновления (в тиках)
update-period: 20

# BossBar
bossbar-text: "§6Привет, {player}! Онлайн: {online}/{max_online}"
bossbar-color: RED        # BLUE, GREEN, PINK, PURPLE, RED, WHITE, YELLOW, REBECCA_PURPLE

# ScoreBoard
scoreboard-title: "§l§cИнформация"
scoreboard-lines:
  - "§fИгрок: §e{player}"
  - "§fОнлайн: §a{online}"
  - "§fСервер: §bCoolServer"
  - ""
  - "§7Время: §fauto"

# ActionBar и Tip
actionbar-text: "§aЭто ActionBar\n§7С новой строки!"
actionbar-offset: 0
tip-text: "§bЭто Tip"

# Опыт
exp-level: "5"
exp-progress: "0.75"

# Команда и Title
title-command: "mytitle"
title-text: "§eГлавный заголовок"
subtitle-text: "§7Подзаголовок"
title-fadein: 10
title-stay: 70
title-fadeout: 20

Как использовать

  1. Скиньте папку BossScore с двумя файлами (plugin.yml, src/BossScore/Main.php) и resources/config.yml в plugins/.
  2. Перезапустите сервер.
  3. Настройте config.yml прямо в папке plugin_data/BossScore/config.yml без перезагрузки сервера — большинство параметров применяются на лету каждые 20 тиков.
  4. Игроки увидят BossBar, ScoreBoard (красные цифры cound 0-14 снизу-вверх), ActionBar, Tip, изменённый опыт.
  5. Команда mytitle (или любая, указанная в title-command) выводит Title/Subtitle нажатием.