Farmstead Mods
EN

Скорборд

Боковая панель из конфига: условия показа, анимация строк, переопределения по группам и игрокам.

На этой странице

Команды 1#

КомандаСинтаксисАлиасыПравоОписание
/scoreboard/scoreboard [toggle | reload | enable | disable | info | preview | board list | set title <board> <text> | set line <board> <index> <text> | player <target> title <text> | player <target> line <index> <text> | player <target> reset | group <group> title <text> | group <group> line <index> <text> | group <group> reset]—neoessentials.scoreboard.adminManage the sidebar scoreboardКорень без .requires — голый `/scoreboard` печатает справку кому угодно; `toggle` закрыт отдельно neoessentials.scoreboard.toggle и только игроку; остальные ветки — neoessentials.scoreboard.admin (консоль проходит всегда); `<target>` — EntityArgument.player, только онлайн; `<board>`/`<group>` — word; `<text>` greedy; `<index>` Integer 0–14. `set`/`player`/`group` и `enable`/`disable` живут в памяти: `/scoreboard reload` и `/neoe reload` возвращают всё из scoreboard.json.

Примеры#

Первая настройка: подобрать заголовок вживую и закрепить его#

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

/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#

КлючПо умолчаниюОписание
scoreboard
scoreboard.enabledtrueSet false to hide the sidebar scoreboard entirely.
scoreboard.refreshInterval10How often the scoreboard updates in ticks (20 = 1s). Lower = smoother, more traffic.
scoreboard.joinDelayTicks40Ticks to wait after a player joins before showing them a board (avoids a flash of content during the join sequence).
scoreboard.boards
scoreboard.boards.name"default"—
scoreboard.boards.priority0—
scoreboard.boards.conditions[]—
scoreboard.boards.title"<gradient:FFD700-FF8C00>&l{server_name}"—
scoreboard.boards.lines
groups
players — Per-player title/line overrides, keyed by player UUID.

Права 2#

УзелВ реестреЧто даёт
neoessentials.scoreboard.adminреестр: нетManage the sidebar scoreboard system
neoessentials.scoreboard.toggleреестр: даToggle your own sidebar scoreboard on/off

Подробное руководство#

Раздел ниже — исходная документация проекта (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:

  1. 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.
  2. /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).
  3. 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