Farmstead Mods
RU

Scoreboard

A config-driven sidebar: display conditions, line animation, per-group and per-player overrides.

On this page

Commands 1#

CommandSyntaxAliasesPermissionDescription
/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 scoreboardThe root has no .requires — bare `/scoreboard` prints help to anyone; `toggle` is gated separately by neoessentials.scoreboard.toggle and is player-only; the other branches need neoessentials.scoreboard.admin (the console always passes); `<target>` — EntityArgument.player, online only; `<board>`/`<group>` — word; `<text>` greedy; `<index>` Integer 0–14. `set`/`player`/`group` and `enable`/`disable` live in memory: `/scoreboard reload` and `/neoe reload` restore everything from scoreboard.json.

Examples#

First setup: tune the title live and then make it permanent#

See what the sidebar looks like without restarting the server, and understand that command edits are temporary.

/scoreboard info
/scoreboard set title default &6&l{server_name}
/scoreboard preview
/scoreboard reload

info prints Enabled/Disabled, the board count, refreshInterval in ticks and board names. set title immediately changes the title for everyone currently shown the default board and collapses the title animation to a single frame. preview rebuilds the panel for you. reload re-reads scoreboard.json (and animations.json too) — the temporary title disappears and the screen shows what is in the file again.

A custom title and line for the vip group#

Test a group's text before writing it into scoreboard.json → scoreboard.groups.

/scoreboard group vip title <gradient:FFD700-FF8C00>&lVIP
/scoreboard group vip line 2 &6Balance: &f${balance} &6[VIP]
/scoreboard info
/scoreboard group vip reset

Players whose primary group is vip immediately see the new title and the replaced line at index 2 (0-based, the third line of the selected board's lines array). Others are unaffected. group vip reset removes the title and all line overrides of that group at once. For the text to survive reload, move it into scoreboard.json → scoreboard.groups.vip (titleOverride / lineOverrides).

A separate board in the Nether#

Show a different set of lines in the_nether without touching the overworld. Conditions are an implicit AND; the first board with the highest priority whose conditions all pass wins.

/scoreboard reload
/scoreboard board list
/scoreboard preview

In scoreboard.json → scoreboard.boards add a board with a priority higher than default (shipped default has 0) and conditions: ["world:the_nether"]. An empty conditions on default always matches — keep it lowest in priority as the fallback. After reload board list lists the names; in the Nether preview shows the new board, in the overworld — default. The dimension name is the path (overworld, the_nether, the_end), not minecraft:the_nether.

A player hides the panel for themselves#

Give players a switch that survives restarts without turning off the whole system.

/scoreboard toggle
/scoreboard toggle

The first call hides the sidebar and writes the state to the DataStore collection scoreboard_toggles (key — UUID). The second brings the panel back. By default the panel is on for a player who has never pressed toggle. Requires the neoessentials.scoreboard.toggle node (the registry grants it to everyone by default). The branch does not run from the console: .requires needs a player.

A multi-frame title and slowing down one board#

Make an animated title without hitting an expensive placeholder every global cycle.

/scoreboard reload
/scoreboard preview

Put a JSON array of strings in the board's title — those are the frames (the loader does not read a separate titleFrames key). Frames cycle by this board's ownAnimFrame. refreshMultiplier (integer ≥ 1, default 1) advances this board's frames once every N global refreshInterval cycles without changing the packet send tick itself. After reload preview shows the first frame; the animation then runs on its own.

Edge cases#

The tree merges with vanilla `/scoreboard`#

NeoEssentials registers the same scoreboard literal, and Brigadier adds child nodes to the vanilla command instead of replacing it. Vanilla objectives / players / teams remain. No name collisions: vanilla has players (plural), here it is player; vanilla enable lives under players, here it is at the root. Bare /scoreboard with no arguments runs the NeoEssentials .executes and prints the mod's help, not vanilla's.

What to do: Vanilla objectives work as before: /scoreboard objectives add …. The mod's help is bare /scoreboard. To remove the mod's command entirely, disable commands.scoreboard in config.json and restart — the vanilla tree remains.

Which board a player sees#

Boards are sorted by descending priority. For each viewer the first board whose conditions lines are all true (implicit AND) is used. An empty condition list always passes — such a board must have the lowest priority, otherwise it overrides the rest. If no board matches, the sidebar is hidden for that player. There is no separate OR — two adjacent priorities with different conditions give the same effect.

What to do: Keep the fallback board last with priority: 0 and conditions: []. Check which board you currently see with /scoreboard preview in the right world and with the right permissions.

`conditions` grammar and the short `{balance}` in a condition#

Parsed in ConditionEvaluator, in order: perm:<node> (PermissionAPI), world:<path> (dimension path, no namespace), {placeholder} == value / != value (case-insensitive comparison); otherwise the string is run through PlaceholderAPI and is true if the result is non-empty and not false.

Conditions do not go through the sidebar's shorthand replacements. In board lines {balance}, {online}, {player} are substituted by ScoreboardManager itself. In a condition, PlaceholderAPI looks for the identifier balance and does not find it (the default expansion is {neoessentials_balance}). An unresolved token stays as the text {balance}: an == comparison fails, and a lone unresolved {token} as a boolean is true because the string is non-empty.

What to do: Check world and permissions with the world: / perm: forms. For placeholders in a condition write {neoessentials_balance}, {neoessentials_group}, {neoessentials_world} and so on. Do not use a lone unknown {token} as a condition — it silently passes.

`lineOverrides` target the array index; hidden lines collapse#

A line can have its own condition. Lines that fail are dropped, and the rest shift up into slots 0…14 — there is no blank gap. An override (lineOverrides in the file or /scoreboard group|player … line) changes only the text and does not cancel the original line's condition: a hidden line stays hidden. The override key is the 0-based index in the selected board's lines array, not the visible slot number after collapsing.

What to do: Count indices by the JSON array, including lines with conditions. If line 2 is hidden by a condition, lineOverrides["2"] still refers to it, not to what the player sees third.

Override precedence and what is lost on reload#

Text chain: player (UUID) → group (PermissionAPI primary) → board value. /scoreboard set title|line edits the board itself in memory (for title, the whole frame list is replaced with one frame). /scoreboard player / group write override maps in memory. loadConfig() on reload fully re-reads boards/groups/players from the file and clears these maps — temporary edits are lost. enable/disable are also memory-only: reload restores scoreboard.enabled from the file.

What to do: Permanent changes go only in scoreboard.json. The dashboard save (saveConfig) can flush the current memory to disk; the commands never write the file themselves.

`{online}` and `{displayname}` on the sidebar#

{online} is server.getPlayerList().getPlayers().size(), without a vanish filter (unlike the tablist). {displayname} is substituted as player.getName(), i.e. the real IGN, not /nick. {max} — server slots. {time} — HH:mm of the system clock, not the world's in-game time.

What to do: Vanished players are still counted in {online}. For a nickname in a line use the PlaceholderAPI nickname placeholder if one is registered, not {displayname}.

Two independent switches#

modules.scoreboardEnabled: false in config.json (restart): the tick/join/quit handler exits immediately, no packets are sent, /scoreboard is not registered.

commands.scoreboard: false (restart): the command is not registered, but the sidebar tick runs as before — the panel stays, you just cannot reload it from the game.

scoreboard.enabled: false + /scoreboard reload (or /scoreboard disable): a live switch-off, the command stays, hideAll is called for everyone (SIDEBAR slot, line teams ne_sb*, objective ne_sidebar).

What to do: To remove the system entirely — modules.scoreboardEnabled. To temporarily hide the panel while keeping the command — enabled in scoreboard.json or /scoreboard disable.

Join delay and anti-flicker#

joinDelayTicks is counted in server ticks (globalTick), not refresh cycles. Until the delay expires, updatePlayer calls hidePlayer. Packets are sent only if the title or the set of visible lines changed since the last send to that viewer. The slot score is fixed (15 − index) with BlankFormat so the client does not reorder lines by number.

What to do: If the panel "flashes" on join, increase joinDelayTicks (shipped 40). If a line does not update, the placeholder most likely returned the same value: the dirty check stays silent on purpose.

Command permissions#

Bare /scoreboard checks no nodes. toggle requires a player and neoessentials.scoreboard.toggle. Admin branches: p == null || neoessentials.scoreboard.admin. Plus the common opsBypassPermissions and PermissionAPI's vanilla OP fallback.

What to do: If /scoreboard "does not exist" even for an operator, check modules.scoreboardEnabled and commands.scoreboard, not permissions. If help works but reload is missing from completion, you lack neoessentials.scoreboard.admin.

Known issues#

The `titleFrames` key from the original wiki is not read#

ScoreboardManager.loadConfig() takes title frames only from title (string or array). The loader has no titleFrames field. Title animation is defined by an array in title. The shipped comment and the ScoreboardSystem.md page disagree with the code.

What to do: Write frames like this: "title": ["frame1", "frame2"]. Do not add a separate titleFrames — it will be ignored.

`toggleCommandEnabled` has no effect#

The original wiki describes a toggleCommandEnabled key. The loader does not read it; the /scoreboard toggle branch is registered with the rest of the command as long as the module and commands.scoreboard are on.

What to do: To take toggle away from players, revoke the neoessentials.scoreboard.toggle node. To remove the whole command — commands.scoreboard: false and a restart.

Saving a board through the web panel drops `refreshMultiplier`#

toConfigJson() / boardToJson() serialize name, priority, conditions, title, lines — and do not write refreshMultiplier. The next save from the panel overwrites scoreboard.json without that field; after reload the multiplier is 1 again.

What to do: Set refreshMultiplier manually in the file and do not save that board with the panel button, or restore the field in the file after saving.

`{displayname}` on the sidebar is the IGN, not the nickname#

resolveText substitutes both {player} and {displayname} via player.getName(). /nick does not affect this line. The placeholder name promises a display name; the code returns the registry name.

What to do: Do not expect a nickname in {displayname} on the sidebar. For lines that need a nickname, use the nickname placeholder that PlaceholderAPI actually resolves and check it with /scoreboard preview.

Configuration#

scoreboard.json#

KeyDefaultDescription
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.

Permissions 2#

NodeIn registryWhat it grants
neoessentials.scoreboard.adminregistry: noManage the sidebar scoreboard system
neoessentials.scoreboard.toggleregistry: yesToggle your own sidebar scoreboard on/off

Detailed guide#

The section below is the project's original documentation (docs/Wiki/ScoreboardSystem.md). Links to other wiki pages are rewritten to this site; the guides themselves are not edited, so upstream syncs stay conflict-free.

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