Скорборд
Боковая панель из конфига: условия показа, анимация строк, переопределения по группам и игрокам.
Команды 1#
Примеры#
Первая настройка: подобрать заголовок вживую и закрепить его#
Увидеть, как выглядит боковая панель, не перезапуская сервер, и понять, что правки из команды временные.
/scoreboard info
/scoreboard set title default &6&l{server_name}
/scoreboard preview
/scoreboard reload
info печатает Enabled/Disabled, число досок, refreshInterval в тиках и имена досок. set title сразу меняет заголовок у всех, кому сейчас показана доска default, и схлопывает анимацию заголовка до одного кадра. preview пересобирает панель лично вам. reload перечитывает scoreboard.json (и заодно animations.json) — временный заголовок исчезает, на экране снова то, что в файле.
Свой заголовок и строка для группы vip#
Проверить текст для группы до того, как вписывать его в scoreboard.json → scoreboard.groups.
/scoreboard group vip title <gradient:FFD700-FF8C00>&lVIP
/scoreboard group vip line 2 &6Баланс: &f${balance} &6[VIP]
/scoreboard info
/scoreboard group vip reset
Игроки, у которых первичная группа vip, сразу видят новый заголовок и заменённую строку с индексом 2 (0-based, это третья строка массива lines выбранной доски). Остальные не затронуты. group vip reset снимает и заголовок, и все line-переопределения этой группы разом. Чтобы текст пережил reload, его нужно перенести в scoreboard.json → scoreboard.groups.vip (titleOverride / lineOverrides).
Отдельная доска в Незере#
Показать другой набор строк в the_nether, не трогая оверворлд. Условия — неявное AND, побеждает первая доска с наибольшим priority, у которой все conditions прошли.
/scoreboard reload
/scoreboard board list
/scoreboard preview
В scoreboard.json → scoreboard.boards добавьте доску с priority больше, чем у default (у поставки default имеет 0), и conditions: ["world:the_nether"]. Пустой conditions у default всегда совпадает — держите его последним по приоритету как запасной вариант. После reload board list перечислит имена; в Незере preview покажет новую доску, в оверворлде — default. Имя измерения — path (overworld, the_nether, the_end), не minecraft:the_nether.
Игрок прячет панель у себя#
Дать игрокам выключатель, который переживает рестарт, не выключая систему целиком.
/scoreboard toggle
/scoreboard toggle
Первый вызов прячет сайдбар и пишет состояние в коллекцию DataStore scoreboard_toggles (ключ — UUID). Второй возвращает панель. По умолчанию у игрока, который ни разу не жал toggle, панель включена. Нужен узел neoessentials.scoreboard.toggle (в реестре он выдан всем по умолчанию). Из консоли ветка не выполняется: .requires требует игрока.
Заголовок из нескольких кадров и замедление одной доски#
Сделать анимированный title и не дёргать дорогой плейсхолдер каждый глобальный цикл.
/scoreboard reload
/scoreboard preview
В title доски поставьте JSON-массив строк — это и есть кадры (отдельного ключа titleFrames загрузчик не читает). Кадры крутятся по ownAnimFrame этой доски. refreshMultiplier (целое ≥ 1, по умолчанию 1) продвигает кадры этой доски раз в N глобальных циклов refreshInterval, не меняя сам тик рассылки пакетов. После reload preview показывает первый кадр; дальше анимация идёт сама.
Тонкие места#
Дерево сливается с ванильным `/scoreboard`#
NeoEssentials регистрирует тот же литерал scoreboard, и Brigadier добавляет дочерние узлы к ванильной команде, а не заменяет её. Ванильные objectives / players / teams остаются. Коллизии имён нет: ваниль — players (мн. ч.), здесь — player; ванильный enable живёт под players, здесь — на корне. Голый /scoreboard без аргументов выполняет .executes NeoEssentials и печатает справку мода, а не ванильную.
Что делать: Ванильные цели по-прежнему: /scoreboard objectives add …. Справка мода — голый /scoreboard. Чтобы совсем убрать команду мода, выключите commands.scoreboard в config.json и перезапустите — ванильное дерево останется.
Какую доску видит игрок#
Доски сортируются по убыванию priority. Для каждого зрителя берётся первая, у которой все строки conditions истинны (неявное AND). Пустой список условий всегда проходит — такая доска обязана иметь наименьший приоритет, иначе перекроет остальные. Если ни одна доска не подошла, сайдбар этому игроку скрывается. Отдельного OR нет — два соседних приоритета с разными условиями дают тот же эффект.
Что делать: Запасную доску держите с priority: 0 и conditions: [] последней. Проверяйте, какую доску вы сейчас видите, через /scoreboard preview в нужном мире и с нужными правами.
Грамматика `conditions` и короткий `{balance}` в условии#
Разбор в ConditionEvaluator, по порядку: perm:<node> (PermissionAPI), world:<path> (path измерения, без namespace), {placeholder} == value / != value (сравнение без учёта регистра), иначе строка прогоняется через PlaceholderAPI и считается истинной, если результат непустой и не false.
Условия не идут через shorthand-замены сайдбара. В строках доски {balance}, {online}, {player} подставляет сам ScoreboardManager. В условии {balance} PlaceholderAPI ищет идентификатор balance и не находит его (дефолтный expansion — {neoessentials_balance}). Нерешённый токен остаётся текстом {balance}: для == сравнение провалится, а одиночный нерешённый {token} как boolean истинен, потому что строка непустая.
Что делать: Мир и права проверяйте формами world: / perm:. Для плейсхолдеров в условии пишите {neoessentials_balance}, {neoessentials_group}, {neoessentials_world} и т. п. Не ставьте одиночный неизвестный {token} как условие — оно молча пройдёт.
`lineOverrides` бьют в индекс массива, скрытые строки сжимаются#
У строки может быть своё condition. Непрошедшие строки выбрасываются, оставшиеся сдвигаются вверх в слоты 0…14 — пустого зазора нет. Переопределение (lineOverrides в файле или /scoreboard group|player … line) меняет только текст и не отменяет condition исходной строки: скрытая строка останется скрытой. Ключ переопределения — 0-based индекс в массиве lines выбранной доски, а не номер видимого слота после сжатия.
Что делать: Считайте индексы по JSON-массиву, включая строки с условием. Если строка 2 скрыта условием, lineOverrides["2"] всё равно относится к ней, а не к тому, что игрок видит третьим.
Приоритет переопределений и что сгорает при reload#
Цепочка текста: игрок (UUID) → группа (первичная PermissionAPI) → значение доски. /scoreboard set title|line правит саму доску в памяти (для title — весь список кадров заменяется одним кадром). /scoreboard player / group пишут карты переопределений в памяти. loadConfig() при reload полностью перечитывает boards/groups/players из файла и очищает эти карты — временные правки пропадают. enable/disable тоже только в памяти: reload вернёт scoreboard.enabled из файла.
Что делать: Постоянное — только в scoreboard.json. Dashboard-запись (saveConfig) умеет сбросить текущую память на диск; команды сами файл не пишут.
`{online}` и `{displayname}` на сайдбаре#
{online} — это server.getPlayerList().getPlayers().size(), без фильтра ваниша (в отличие от таблиста). {displayname} подставляется как player.getName(), то есть настоящий IGN, а не /nick. {max} — слоты сервера. {time} — HH:mm системных часов, не игровое время мира.
Что делать: Скрытых в ванише всё равно считают в {online}. Для ника в строке используйте плейсхолдер ника из PlaceholderAPI, если он зарегистрирован, а не {displayname}.
Два независимых выключателя#
modules.scoreboardEnabled: false в config.json (рестарт): обработчик тика/join/quit сразу выходит, пакеты не отправляются, /scoreboard не регистрируется.
commands.scoreboard: false (рестарт): команда не регистрируется, но тик сайдбара идёт как раньше — панель остаётся, просто её нельзя перезагрузить из игры.
scoreboard.enabled: false + /scoreboard reload (или /scoreboard disable): живое выключение, команда на месте, у всех вызывается hideAll (слот SIDEBAR, команды строк ne_sb*, objective ne_sidebar).
Что делать: Убрать систему целиком — modules.scoreboardEnabled. Временно спрятать панель, оставив команду, — enabled в scoreboard.json либо /scoreboard disable.
Задержка после входа и анти-мигание#
joinDelayTicks считается в серверных тиках (globalTick), не в циклах refresh. Пока задержка не истекла, updatePlayer вызывает hidePlayer. Пакеты уходят, только если заголовок или набор видимых строк изменились с прошлой отправки этому зрителю. Счёт очков слота фиксирован (15 − index) с BlankFormat, чтобы клиент не переставлял строки по числу.
Что делать: Если панель «вспыхивает» на входе — увеличьте joinDelayTicks (поставка 40). Если строка не обновляется — плейсхолдер, скорее всего, вернул то же самое значение: dirty-check намеренно молчит.
Права на команду#
Голый /scoreboard не проверяет узлы. toggle требует игрока и neoessentials.scoreboard.toggle. Админ-ветки: p == null || neoessentials.scoreboard.admin. Плюс общие opsBypassPermissions и ванильный OP-fallback PermissionAPI.
Что делать: Если /scoreboard «не существует» даже у оператора — смотрите modules.scoreboardEnabled и commands.scoreboard, не права. Если есть справка, но нет reload в автодополнении — нет neoessentials.scoreboard.admin.
Известные проблемы#
Ключ `titleFrames` из оригинальной вики не читается#
ScoreboardManager.loadConfig() берёт кадры заголовка только из title (строка или массив). Поля titleFrames в JSON нет в загрузчике. Анимация заголовка задаётся массивом в title. Комментарий в поставке и страница ScoreboardSystem.md расходятся с кодом.
Что делать: Пишите кадры так: "title": ["кадр1", "кадр2"]. Не заводите отдельный titleFrames — он будет проигнорирован.
`toggleCommandEnabled` ни на что не влияет#
Оригинальная вики описывает ключ toggleCommandEnabled. Загрузчик его не читает, ветка /scoreboard toggle регистрируется вместе с остальной командой, пока включены модуль и commands.scoreboard.
Что делать: Чтобы отнять toggle у игроков, заберите узел neoessentials.scoreboard.toggle. Чтобы убрать всю команду — commands.scoreboard: false и рестарт.
Запись доски через веб-панель выбрасывает `refreshMultiplier`#
toConfigJson() / boardToJson() сериализуют name, priority, conditions, title, lines — и не пишут refreshMultiplier. Следующий save с панели перезапишет scoreboard.json без этого поля; после reload множитель снова станет 1.
Что делать: Задавайте refreshMultiplier вручную в файле и не сохраняйте эту доску кнопкой панели, либо верните поле в файл после save.
`{displayname}` на сайдбаре — это IGN, не ник#
resolveText подставляет и {player}, и {displayname} через player.getName(). /nick на эту строку не действует. Имя плейсхолдера обещает display name, код отдаёт реестровое имя.
Что делать: Не ждите ника в {displayname} на сайдбаре. Для строк, где нужен ник, используйте тот плейсхолдер ника, который реально резолвит PlaceholderAPI, и проверьте его /scoreboard preview.
Конфигурация#
scoreboard.json#
Права 2#
Подробное руководство#
Раздел ниже — исходная документация проекта (docs/Wiki/ScoreboardSystem.md). Ссылки на другие страницы вики переписаны на этот сайт; текст самих руководств не редактируется, чтобы синк с апстримом не давал конфликтов.
Config file: config/neoessentials/scoreboard.json
Reload in-game: /scoreboard reload
Admin permission: neoessentials.scoreboard.admin · Toggle permission: neoessentials.scoreboard.toggle
Introduced: v1.0.6
Overview#
The sidebar scoreboard system renders the classic right-hand-side Minecraft scoreboard —
title + up to 15 lines — fully server-side, config-driven, with animation, conditional
boards, and per-group/per-player overrides.
| Feature |
Details |
| Named boards |
Multiple boards, each with its own conditions; the first matching one wins |
| Conditions |
perm:<node>, world:<name>, {placeholder} == value / != value |
| Animated title/lines |
Frame arrays, cycled at refreshInterval |
| Per-group / per-player overrides |
Title and individual line overrides, same priority order as Tablist |
| Persisted per-player toggle |
/scoreboard toggle remembers each player's preference across sessions |
| Join delay |
Configurable grace period before a player's board first appears |
| True per-viewer rendering |
Unlike a vanilla /scoreboard objective (server-wide, one value for everyone), different players can see different boards or different placeholder values simultaneously — see Technical Notes |
| Dashboard endpoint |
/api/scoreboard — read/create/update boards, toggle enabled |
Why This Isn't Just a Vanilla /scoreboard Objective#
Vanilla's scoreboard Objective/Team/Score state lives on the server's single shared
ServerScoreboard and is broadcast identically to every connected player. That's fine for a
tablist row (a player's own nametag looks the same to every viewer) but wrong for a sidebar:
two players might be shown different boards (one in the Nether, one in the Overworld) or the
same board with different values (each player's own balance).
NeoEssentials solves this by building the four scoreboard packets
(ClientboundSetObjectivePacket, ClientboundSetPlayerTeamPacket, ClientboundSetScorePacket,
ClientboundResetScorePacket) against a private, never-registered Scoreboard instance
purely to serialize them, then sending them directly to one player's connection — the
same technique already used for the tablist header/footer packet. No two players' boards can
interfere with each other, and nothing is ever visible to a player other than the one the
packet was sent to.
Configuration#
Full scoreboard.json structure#
{
"_configVersion": 1,
"scoreboard": {
"enabled": true,
"refreshInterval": 20, // ticks between updates (20 = 1s)
"joinDelayTicks": 40, // wait this long after join before showing a board
"toggleCommandEnabled": true,
"boards": [
{
"name": "default",
"priority": 0,
"conditions": [], // empty = always matches — MUST be last (fallback)
"title": "<gradient:FFD700-FF8C00>&l{server_name}",
"titleFrames": [], // optional: animated title, same shape as tablist header
"lines": [
{ "text": "&7&m " },
{ "text": "&eOnline: &f{online}&7/&f{max}" },
{ "text": "&eBalance: &f${balance}" },
{ "text": "&eGroup: &f{group}" },
{ "text": "&6Richest: &f{leaderboard_money:1:name}" }
]
},
{
"name": "nether_board",
"priority": 10,
"conditions": [ "world:the_nether" ],
"title": "&c&lNETHER",
"lines": [ { "text": "&7Danger zone" } ]
}
],
"groups": { },
"players": { }
}
}
Core Fields#
| Field |
Type |
Default |
Description |
enabled |
boolean |
true |
Enable/disable the whole system |
refreshInterval |
int |
20 |
Update interval in ticks |
joinDelayTicks |
int |
40 |
Ticks after join before the board first appears |
boards |
array |
— |
Named board definitions (see below) |
groups |
object |
— |
Per-group titleOverride/lineOverrides |
players |
object |
— |
Per-player UUID titleOverride/lineOverrides |
Board Fields#
| Field |
Type |
Description |
name |
string |
Board identifier |
priority |
int |
Boards are evaluated highest-priority first; first whose conditions all pass wins |
conditions |
array |
List of condition strings, implicit AND. Empty = always matches |
title |
string or array |
Static string, or array for animated frames |
titleFrames |
array |
Alternate way to specify animated title frames |
lines |
array |
Up to 15 { "text": ..., "condition": ... } objects |
A line's own condition (optional, single string) controls whether that specific line is
shown — hidden lines are skipped entirely (remaining visible lines compact upward into
sequential slots), not left as a blank gap.
Conditions Grammar#
| Form |
Matches when |
perm:<node> |
The player has that permission |
world:<name> |
The player's current dimension path equals <name> (e.g. overworld, the_nether, the_end) |
{placeholder} == value |
The resolved placeholder equals value (case-insensitive) |
{placeholder} != value |
The resolved placeholder does not equal value |
| anything else |
Resolved as a placeholder and treated as boolean ("false"/empty = false) |
This is deliberately a small comparison grammar, not a scripting engine — it covers
permission gates, world checks, and placeholder-threshold checks, which covers the vast
majority of real use cases without the security/maintenance surface of an embedded
expression language.
A board's conditions list is implicit AND. There's no OR — approximate it with two boards
at adjacent priorities if you need it.
Overrides#
Same priority order as Tablist: per-player > per-group > board default.
"groups": {
"vip": {
"titleOverride": "&6&lVIP SERVER",
"lineOverrides": { "2": "&6Balance: &f${balance} &6[VIP]" }
}
},
"players": {
"<uuid>": {
"titleOverride": "&dWelcome back!"
}
}
lineOverrides keys are 0-based indexes into the resolved board's lines array. An override
replaces the line's text but doesn't change its condition — a conditionally-hidden line
stays hidden even with an override text set.
Placeholders#
Shorthand tokens (matching tablist's convention):
| Placeholder |
Description |
{player} {displayname} |
Viewing player's name |
{online} {max} |
Online count / max slots |
{ping} |
Viewing player's ping |
{world} |
Current dimension path |
{balance} |
Economy balance |
{prefix} {suffix} {group} |
Permission group data |
{x} {y} {z} |
Block coordinates |
{server_name} |
Plain server name (general.serverName in config.json) |
{server_motd} |
Configured MOTD (/motd if set, otherwise server.properties) — usually multi-line/formatted for the server list, not a great fit for a scoreboard row |
{time} |
Server time (HH:mm) |
Plus {animation:NAME} (from animations.json, shared with the Tablist System),
the full {neoessentials_*} registered placeholder set, and
{leaderboard_<board>:<rank>:name|value} — see Leaderboard System.
Commands#
| Command |
Permission |
Description |
/scoreboard |
— |
Show help |
/scoreboard toggle |
neoessentials.scoreboard.toggle |
Show/hide your own sidebar (persisted) |
/scoreboard reload |
neoessentials.scoreboard.admin |
Reload scoreboard.json and push to all players |
/scoreboard enable / disable |
admin |
Enable/disable the whole system |
/scoreboard info |
admin |
Status summary |
/scoreboard preview |
admin |
Re-send your own board now |
/scoreboard board list |
admin |
List configured boards |
/scoreboard set title <board> <text> |
admin |
Replace a board's title (single static frame) |
/scoreboard set line <board> <index> <text> |
admin |
Replace one line's text (0-based index) |
/scoreboard player <player> title\|line\|reset |
admin |
Per-player overrides |
/scoreboard group <group> title\|line\|reset |
admin |
Per-group overrides |
Runtime overrides via /scoreboard player//scoreboard group are not persisted — add
them to scoreboard.json manually to survive /scoreboard reload.
Toggle Persistence#
/scoreboard toggle is backed by its own DataStore collection (scoreboard_toggles, one
record per player, written immediately on change) — the same single-collection,
write-immediately pattern used by PayToggleManager for /paytoggle. A player's preference
defaults to on and survives server restarts.
Disabling Completely#
Three independent layers, from most to least aggressive:
modules.scoreboardEnabled: false in config.json (restart required) — the event
handlers driving tick/join/quit never fire at all, so no scoreboard packet is ever sent.
Same restart-required module-toggle pattern as every other subsystem.
/scoreboard disable (live, no restart) — immediately tears down the sidebar for
every currently-online player: clears the display slot, resets every visible line's score,
removes the per-line teams, and removes the objective itself (not just hides it).
scoreboard.json's "enabled": false + /scoreboard reload — same effect as #2.
Technical Notes#
- Fake score-holder entries. Each of the 15 possible line slots uses a fixed invisible
string (
§0–§e) as its score-holder name — these aren't real players, just unique
client-side row identifiers. The actual line text lives entirely in that slot's team prefix.
- Anti-flicker. Packets are only sent when the player's resolved title/lines actually
changed since the last send — same dirty-check philosophy as the Tablist System's team
prefix caching.
- Score ordering. Vanilla's client sorts sidebar rows by descending score value; each
slot is given a fixed score (
15 − slotIndex) so line order stays stable regardless of
content, with the number itself hidden via a blank NumberFormat.
Web Dashboard API#
/api/scoreboard (auth required for reads, auth-admin for writes):
| Method |
Path |
Description |
GET |
/api/scoreboard |
Full config overview |
GET |
/api/scoreboard/boards/{name} |
Single board detail |
PUT |
/api/scoreboard/enabled |
{ enabled } |
POST |
/api/scoreboard/boards |
Create/update a board |
DELETE |
/api/scoreboard/boards/{name} |
Delete a board |
PUT |
/api/scoreboard/boards/{name}/line/{index} |
Quick single-line edit |
Back to Wiki Home