Перейти к основному содержимому

Руководство по скорбордам

В Nukkit-MOT есть полноценный API скорбордов для отображения в боковой панели, списке игроков и под именем. Он поддерживает строки с произвольным текстом, строки, привязанные к игрокам, строки, привязанные к сущностям, регистрацию целей (objective) на уровне менеджера и хранение на основе JSON.

Область, основанная на исходном коде

Эта страница написана по текущему исходному коду Nukkit-MOT, особенно IScoreboardManager, ScoreboardManager, IScoreboard, Scoreboard, ScoreboardLine, FakeScorer, PlayerScorer, EntityScorer и встроенной команде /scoreboard.

Импорт правильного класса Scoreboard

В дереве исходного кода есть два разных класса Scoreboard:

  • текущий API: cn.nukkit.scoreboard.scoreboard.Scoreboard
  • устаревшая обёртка совместимости: cn.nukkit.scoreboard.Scoreboard

Для нового кода плагинов используйте класс из пакета cn.nukkit.scoreboard.scoreboard.

Основная модель

Основные компоненты:

ТипРоль
IScoreboardManagerГлобальный реестр, назначение слотов отображения, доступ к хранилищу
IScoreboard / ScoreboardОдна цель с отображаемым именем, критерием, порядком сортировки, наблюдателями и строками
IScoreboardLine / ScoreboardLineОдин носитель значения (scorer) + одно значение счёта
IScorerВладелец строки: произвольный текст, игрок или сущность
DisplaySlotSIDEBAR, LIST, BELOW_NAME
SortOrderASCENDING или DESCENDING

Важная точка входа:

IScoreboardManager manager = this.getServer().getScoreboardManager();

1. Приватная боковая панель для одного игрока

Если вы хотите показать боковую панель только одному игроку, вам не нужна система слотов отображения глобального менеджера. Вы можете создать скорборд и напрямую подключить этого игрока как наблюдателя.

import cn.nukkit.Player;
import cn.nukkit.network.protocol.types.DisplaySlot;
import cn.nukkit.network.protocol.types.SortOrder;
import cn.nukkit.scoreboard.scoreboard.IScoreboard;
import cn.nukkit.scoreboard.scoreboard.Scoreboard;
import cn.nukkit.scoreboard.scorer.FakeScorer;

public void showProfileSidebar(Player player, int kills, int coins) {
IScoreboard scoreboard = new Scoreboard("profile_sidebar", "Profile", "dummy", SortOrder.DESCENDING);

FakeScorer killsLine = new FakeScorer("Kills");
FakeScorer coinsLine = new FakeScorer("Coins");

scoreboard.addLine(killsLine, kills);
scoreboard.addLine(coinsLine, coins);
scoreboard.addViewer(player, DisplaySlot.SIDEBAR);
}

Чтобы снова скрыть её:

scoreboard.removeViewer(player, DisplaySlot.SIDEBAR);

Этот подход полезен для индивидуального интерфейса игрока, которому не нужны интеграция с командами или глобальный поиск целей.

2. Обновление значений

Сохраняйте ссылку на scorer или строку, если хотите обновить её позже. ScoreboardLine#setScore(...) автоматически отправляет обновление текущим наблюдателям.

import cn.nukkit.scoreboard.scoreboard.IScoreboardLine;
import cn.nukkit.scoreboard.scorer.FakeScorer;

FakeScorer killsLine = new FakeScorer("Kills");
scoreboard.addLine(killsLine, 0);

IScoreboardLine line = scoreboard.getLine(killsLine);
if (line != null) {
line.setScore(12);
}

Также можно удалять строки:

scoreboard.removeLine(killsLine);

3. Массовая пересборка и resend()

При множестве одновременных изменений пересоберите скорборд в памяти и отправьте его заново, вместо того чтобы отправлять десятки инкрементальных обновлений.

import cn.nukkit.scoreboard.scorer.FakeScorer;

scoreboard.removeAllLine(false);
scoreboard.addLine(new FakeScorer("Kills"), kills);
scoreboard.addLine(new FakeScorer("Coins"), coins);
scoreboard.addLine(new FakeScorer("Rank"), rankPoints);
scoreboard.resend();

Есть также сокращение для простых боковых панелей с произвольным текстом:

scoreboard.setLines(List.of(
"Profile",
"Kills",
"Coins"
));

setLines(List<String>) пересобирает все строки с использованием FakeScorer, а затем вызывает resend().

4. Глобальные цели через ScoreboardManager

Регистрируйте скорборд в менеджере, если хотите, чтобы он вёл себя как настоящая серверная цель:

  • доступность по имени цели
  • возможность использования командой /scoreboard
  • видимость для функций, которые ищут цели счёта по имени
  • возможность сохранения через хранилище скорбордов
import cn.nukkit.scoreboard.manager.IScoreboardManager;
import cn.nukkit.scoreboard.scoreboard.IScoreboard;
import cn.nukkit.scoreboard.scoreboard.Scoreboard;

IScoreboardManager manager = this.getServer().getScoreboardManager();
IScoreboard scoreboard = new Scoreboard("kills", "Kills", "dummy");

if (!manager.containScoreboard(scoreboard.getObjectiveName())) {
manager.addScoreboard(scoreboard);
}

Если позже вы захотите удалить его:

manager.removeScoreboard("kills");

5. Слоты отображения менеджера против прямых наблюдателей

Существует два стиля отображения:

Прямые наблюдатели у объекта скорборда

Используйте scoreboard.addViewer(player, slot), когда нужен точный контроль для каждого игрока.

Отображение слота под управлением менеджера

Используйте manager.setDisplay(slot, scoreboard), когда хотите назначить один скорборд на глобальный слот для зарегистрированных наблюдателей менеджера.

import cn.nukkit.network.protocol.types.DisplaySlot;

manager.setDisplay(DisplaySlot.SIDEBAR, scoreboard);

Чтобы очистить слот:

manager.setDisplay(DisplaySlot.SIDEBAR, null);

Если вы используете отображение на уровне менеджера в коде плагина, регистрируйте и удаляйте наблюдателей самостоятельно:

manager.addViewer(player);
manager.removeViewer(player);

6. Выбор типа Scorer

Тип scorer определяет, к чему привязана строка.

ScorerДля чего подходитПримечания
FakeScorerТекстовые строки вроде Kills, Coins, пустые разделителиРавенство определяется по тексту fake-имени
PlayerScorerРеальные значения, привязанные к игрокамПакеты собираются только когда игрок в сети
EntityScorerЗначения, привязанные к сущностямПолезно для строк, управляемых селекторами или привязанных к конкретным сущностям

Пример: скорборд в стиле здоровья под именем

BELOW_NAME наиболее полезен с PlayerScorer, поскольку строка связана с реальным игроком.

import cn.nukkit.Player;
import cn.nukkit.network.protocol.types.DisplaySlot;
import cn.nukkit.scoreboard.scoreboard.IScoreboard;
import cn.nukkit.scoreboard.scoreboard.Scoreboard;
import cn.nukkit.scoreboard.scorer.PlayerScorer;

IScoreboard healthBoard = new Scoreboard("health", "HP", "dummy");

for (Player target : this.getServer().getOnlinePlayers().values()) {
healthBoard.addLine(new PlayerScorer(target), (int) target.getHealth());
}

for (Player viewer : this.getServer().getOnlinePlayers().values()) {
healthBoard.addViewer(viewer, DisplaySlot.BELOW_NAME);
}

Когда здоровье игрока меняется, обновите строку этого игрока:

PlayerScorer scorer = new PlayerScorer(player);
if (healthBoard.containLine(scorer)) {
healthBoard.getLine(scorer).setScore((int) player.getHealth());
}

7. Порядок сортировки и слоты

Текущие слоты отображения:

  • DisplaySlot.SIDEBAR
  • DisplaySlot.LIST
  • DisplaySlot.BELOW_NAME

Текущие режимы сортировки:

  • SortOrder.ASCENDING
  • SortOrder.DESCENDING

SortOrder важнее всего для SIDEBAR и LIST. BELOW_NAME обычно используется с одним значением на отслеживаемого игрока, а не как ранжированный список.

8. Сохранение и scoreboard.json

Сервер создаёт ScoreboardManager с JSONScoreboardStorage, который опирается на файл scoreboard.json в пути данных сервера.

Сохранить и перезагрузить данные можно через менеджер:

manager.save();
manager.read();

Важная деталь: изменения скорборда не сохраняются автоматически при каждом добавлении или изменении строки. Если ваш плагин рассчитывает на сохранение состояния скорборда после перезапуска, явно вызывайте manager.save() в подходящий момент.

9. Безопасность основного потока

API скорбордов взаимодействуют с пакетами Player и состоянием наблюдателей. Относитесь к созданию, обновлению и изменению отображения скорборда как к работе в основном потоке.

Если значение приходит из медленного ввода-вывода или тяжёлых вычислений:

  1. вычислите его асинхронно
  2. вернитесь в основной поток
  3. обновите скорборд уже там

Шаблон передачи между потоками описан в руководстве по планировщику.

Частые ошибки

1. Импорт устаревшего cn.nukkit.scoreboard.Scoreboard

Этот класс существует только для совместимости со старыми плагинами. Для новой работы используйте cn.nukkit.scoreboard.scoreboard.Scoreboard.

2. Повторное использование одного и того же fake-текста

Равенство FakeScorer определяется его текстом. Если вы используете одно и то же fake-имя дважды, более поздняя строка заменит более раннюю.

Для разделителей боковой панели или нескольких визуально пустых строк используйте разные строки, например с разными кодами форматирования.

3. Предположение, что addScoreboard(...) отклоняет дубликаты

Текущий ScoreboardManager.addScoreboard(...) перезаписывает цель по её имени. Проверяйте containScoreboard(name) самостоятельно перед добавлением.

4. Ожидание нормального отображения строк PlayerScorer для офлайн-игроков

PlayerScorer.toNetworkInfo(...) возвращает null, когда игрок не в сети, поэтому такие строки отфильтровываются при отправке пакетов. Используйте FakeScorer, если нужны чисто текстовые строки или безопасные при офлайне метки.

5. Отношение к отображению менеджера как к автоматическому для каждого игрока

manager.setDisplay(...) отправляет данные только текущим наблюдателям менеджера. В коде плагина явно добавляйте или удаляйте наблюдателей, если вы полагаетесь на поведение отображения на уровне менеджера.