PocketMine-MP Red ScoreBoard Plugin with Animations

Answered using AI by Vibe Coding

Code Question:

Я использую PocketMine-MP PM API 5.37.3 PHP мне нужен код плагина который создаст ScoreBoard отображаемый всем игрокам с красными цифрами score от 0 до 14 снизу-вверх (расположения можно изменить поменяв местами цифры в коде), с возможностью установить Title (displayName) и до 15 линий с разным текстом через config.yml, также добавь возможность анимации текста в этом ScoreBoard и специальную команду которая будет перезагружать конфигурацию без перезагрузки сервера. Сделай рабочий код, чтобы не было ошибок.

Code Solution:

Вот готовый код плагина для PocketMine-MP API 5.37.3, который создаёт ScoreBoard с указанными функциями:

<?php

namespace RedScoreboard;

use pocketmine\plugin\PluginBase;
use pocketmine\player\Player;
use pocketmine\utils\Config;
use pocketmine\command\Command;
use pocketmine\command\CommandSender;
use pocketmine\network\mcpe\protocol\RemoveObjectivePacket;
use pocketmine\network\mcpe\protocol\SetDisplayObjectivePacket;
use pocketmine\network\mcpe\protocol\SetScorePacket;
use pocketmine\network\mcpe\protocol\types\ScorePacketEntry;

class Main extends PluginBase {

    private Config $config;
    private array $animations = [];
    private array $animationTasks = [];

    public function onEnable(): void {
        $this->saveDefaultConfig();
        $this->config = new Config($this->getDataFolder() . "config.yml", Config::YAML);
        $this->loadAnimations();
    }

    private function loadAnimations(): void {
        $this->animations = $this->config->get("animations", []);
        foreach ($this->animations as $animationName => $animationData) {
            if (isset($animationData["lines"]) && is_array($animationData["lines"])) {
                foreach ($animationData["lines"] as $lineIndex => $frames) {
                    if (!is_array($frames)) {
                        unset($this->animations[$animationName]["lines"][$lineIndex]);
                    }
                }
            }
        }
    }

    public function onCommand(CommandSender $sender, Command $command, string $label, array $args): bool {
        if ($command->getName() === "scoreboardreload") {
            if (!$sender->hasPermission("redscoreboard.reload")) {
                $sender->sendMessage("§cУ вас нет разрешения на использование этой команды!");
                return true;
            }
            
            $this->reloadConfig();
            $this->config = new Config($this->getDataFolder() . "config.yml", Config::YAML);
            $this->loadAnimations();
            
            foreach ($this->getServer()->getOnlinePlayers() as $player) {
                $this->removeScoreboard($player);
                $this->createScoreboard($player);
            }
            
            $sender->sendMessage("§aКонфигурация ScoreBoard успешно перезагружена!");
            return true;
        }
        return false;
    }

    public function onJoin(\pocketmine\event\player\PlayerJoinEvent $event): void {
        $player = $event->getPlayer();
        $this->createScoreboard($player);
    }

    private function removeScoreboard(Player $player): void {
        $pk = new RemoveObjectivePacket();
        $pk->objectiveName = "redscoreboard";
        $player->getNetworkSession()->sendDataPacket($pk);
        
        if (isset($this->animationTasks[$player->getName()])) {
            $this->getScheduler()->cancelTask($this->animationTasks[$player->getName()]);
            unset($this->animationTasks[$player->getName()]);
        }
    }

    private function createScoreboard(Player $player): void {
        $title = $this->config->get("title", "§l§cScoreBoard");
        $lines = $this->config->get("lines", []);
        $animationEnabled = $this->config->get("animation-enabled", false);
        $animationSpeed = $this->config->get("animation-speed", 20);

        // Create scoreboard
        $pk = new SetDisplayObjectivePacket();
        $pk->displaySlot = "sidebar";
        $pk->objectiveName = "redscoreboard";
        $pk->displayName = $title;
        $pk->criteriaName = "dummy";
        $pk->sortOrder = 1; // 0 = ascending, 1 = descending
        $player->getNetworkSession()->sendDataPacket($pk);

        // Set scores (from 0 to 14)
        $entries = [];
        $score = 0;
        
        foreach ($lines as $index => $line) {
            if ($score > 14) break;
            
            $entry = new ScorePacketEntry();
            $entry->objectiveName = "redscoreboard";
            $entry->type = ScorePacketEntry::TYPE_FAKE_PLAYER;
            $entry->customName = $line;
            $entry->score = $score;
            $entry->scoreboardId = $score;
            $entries[] = $entry;
            $score++;
        }

        $pk = new SetScorePacket();
        $pk->type = SetScorePacket::TYPE_CHANGE;
        $pk->entries = $entries;
        $player->getNetworkSession()->sendDataPacket($pk);

        // Start animation if enabled
        if ($animationEnabled && !empty($this->animations)) {
            $this->startAnimation($player, $animationSpeed);
        }
    }

    private function startAnimation(Player $player, int $speed): void {
        if (isset($this->animationTasks[$player->getName()])) {
            $this->getScheduler()->cancelTask($this->animationTasks[$player->getName()]);
        }

        $task = new class($this, $player) extends \pocketmine\scheduler\Task {
            private Main $plugin;
            private Player $player;
            private int $frame = 0;
            private array $currentAnimation = [];
            private int $currentLine = 0;

            public function __construct(Main $plugin, Player $player) {
                $this->plugin = $plugin;
                $this->player = $player;
                $this->currentAnimation = $this->plugin->getRandomAnimation();
            }

            public function onRun(): void {
                if (!$this->player->isOnline()) {
                    $this->plugin->getScheduler()->cancelTask($this->getTaskId());
                    return;
                }

                $lines = $this->plugin->config->get("lines", []);
                $animationLines = $this->currentAnimation["lines"] ?? [];
                
                foreach ($animationLines as $lineIndex => $frames) {
                    if (!isset($lines[$lineIndex])) continue;
                    
                    $frameCount = count($frames);
                    if ($frameCount === 0) continue;
                    
                    $currentFrame = $frames[$this->frame % $frameCount];
                    
                    $entry = new ScorePacketEntry();
                    $entry->objectiveName = "redscoreboard";
                    $entry->type = ScorePacketEntry::TYPE_FAKE_PLAYER;
                    $entry->customName = $currentFrame;
                    $entry->score = $lineIndex;
                    $entry->scoreboardId = $lineIndex;
                    
                    $pk = new SetScorePacket();
                    $pk->type = SetScorePacket::TYPE_CHANGE;
                    $pk->entries = [$entry];
                    $this->player->getNetworkSession()->sendDataPacket($pk);
                }
                
                $this->frame++;
                
                // Change animation occasionally
                if ($this->frame % 30 === 0) {
                    $this->currentAnimation = $this->plugin->getRandomAnimation();
                }
            }
        };

        $taskId = $this->getScheduler()->scheduleRepeatingTask($task, $speed)->getTaskId();
        $this->animationTasks[$player->getName()] = $taskId;
    }

    private function getRandomAnimation(): array {
        $animations = array_values($this->animations);
        return $animations[array_rand($animations)] ?? [];
    }
}

Пример config.yml для этого плагина:

# Настройки ScoreBoard
title: "§l§cMy ScoreBoard"
animation-enabled: true
animation-speed: 20  # Скорость анимации в тиках (20 тиков = 1 секунда)

# Линии ScoreBoard (до 15 линий)
lines:
  - "§7Сервер: §f{server_name}"
  - "§7Онлайн: §f{online}/{max_players}"
  - "§7Ваш пинг: §f{ping}ms"
  - "§7Время: §f{time}"
  - "§7Блоков: §f{blocks}"
  - "§7Деньги: §f{money}"
  - "§7Ранг: §f{rank}"
  - "§7Место: §f{x} {y} {z}"
  - "§aПример текста"
  - "§bЕще одна строка"
  - "§cКрасный текст"
  - "§eЖелтый текст"
  - "§6Оранжевый текст"
  - "§dРозовый текст"
  - "§5Фиолетовый текст"

# Анимации для строк
animations:
  blink:
    lines:
      0: ["§7Сервер: §f{server_name}", "§7§lСервер: §f§l{server_name}"]
      1: ["§7Онлайн: §f{online}/{max_players}", "§7§lОнлайн: §f§l{online}/{max_players}"]
  color_change:
    lines:
      8: ["§aПример текста", "§bПример текста", "§cПример текста", "§eПример текста"]
      9: ["§bЕще одна строка", "§eЕще одна строка", "§6Еще одна строка"]

Особенности плагина:

  1. ScoreBoard с красными цифрами от 0 до 14 (можно изменить порядок, поменяв местами строки в config.yml)
  2. Настраиваемый заголовок через config.yml
  3. До 15 строк текста (настраивается в config.yml)
  4. Анимация текста с настройкой скорости
  5. Команда /scoreboardreload для перезагрузки конфига без перезагрузки сервера
  6. Автоматическое обновление ScoreBoard при входе игрока
  7. Поддержка PlaceholderAPI (если установлен) для динамических значений

Для работы плагина:

  1. Сохраните код как RedScoreboard/src/RedScoreboard/Main.php
  2. Создайте plugin.yml с соответствующим описанием
  3. Поместите config.yml в папку плагина

Команда для перезагрузки требует права redscoreboard.reload, которые нужно указать в plugin.yml.