Farmstead Mods
RU

Leaderboards

Ranking boards from vanilla statistics, the economy and custom scores, with a GUI and holograms.

On this page

Commands 1#

CommandSyntaxAliasesPermissionDescription
/leaderboard/leaderboard [<board> [page | gui] | reload | admin set <board> <player> <value> | admin add <board> <player> <delta> | admin reset <board> <player> | admin create <id> <displayName> | admin delete <board> | hologram create <board> <id> [lines]]lbneoessentials.leaderboard.viewView ranked leaderboards (money, kills, playtime, ...)The `/lb` alias is registered by the same tree. Root — neoessentials.leaderboard.view (console passes); `reload` / `admin` / `hologram` — neoessentials.leaderboard.admin. `<board>`/`<id>`/`<player>` — word; `<displayName>` greedy; `<value>`/`<delta>` — long; `<page>` ≥ 1; chat pages hold 10 lines, the GUI 45. `gui` and `hologram create` are player-only. set/add/reset/delete work only on type `custom`. The shipped config has no separate `modules.leaderboardEnabled` or `commands.leaderboard` key — the command is always registered. `/baltop` does not use this system.

Examples#

View the balance top in chat and in a chest#

Check that the shipped money board is alive and that the GUI is the same ranking, just a different view.

/leaderboard
/leaderboard money
/leaderboard money 2
/leaderboard money gui

Bare /leaderboard prints the ids of registered boards (the shipped set includes at least money, kills, mob_kills, playtime, shop_sales; boards disabled in JSON are not registered). /leaderboard money — the first page of 10 lines, with a header showing the page number and cache age in seconds. If the cache is still building, you get a building message instead of "empty" — wait and retry. gui opens a 6×9 chest: player heads, navigation on slots 48–50. gui cannot be opened from the console.

Enable the shipped "diamond ore" board#

leaderboard.json already contains diamonds_mined with enabled: false. Enable it without touching Java.

/leaderboard reload
/leaderboard
/leaderboard diamonds_mined

Set enabled: true on the diamonds_mined entry and run reload (or /neoe reload — it also calls LeaderboardConfigLoader.load()). The board appears in the list. stat is the vanilla criteria string minecraft.mined:minecraft.diamond_ore: only regular diamond ore counts, not deepslate. Offline players are read from <world>/stats/<uuid>.json; anyone with a file is in the table, even with zero.

Custom event points#

Create a board that vanilla statistics do not have and award points to an offline player.

/leaderboard admin create event_points Event Points
/leaderboard admin set event_points Steve 50
/leaderboard admin add event_points Steve 10
/leaderboard event_points
/leaderboard admin reset event_points Steve

If the id is taken — board_exists error. create appends a type: custom object to leaderboard.json and registers the board immediately. set/add/reset resolve the UUID among online players first, then via the profile cache — an offline name with a known profile works; completion suggestions show online players only. Values are written to the DataStore collection leaderboard_custom (key <boardId>:<uuid>) and invalidate that board's cache immediately. reset is set … 0. Economy or vanilla_stat boards are rejected by these branches: not_custom_board.

Balance top hologram#

Avoid typing 10 lines of /hologram addline by hand — the generator places a title + N placeholders {leaderboard_<board>:<rank>:name|value}.

/leaderboard hologram create money top_balances 5
/hologram setrefresh top_balances 30

You need the holograms module, a player (not the console) and a free id. The hologram spawns at your feet +1.5 on Y, id is lowercased, the generator's refreshInterval is 5 seconds. lines defaults to 10, max 15 (Brigadier 1–15). It is an ordinary hologram: from then on manage it with /hologram. If the holograms module is off — holograms_disabled. If the id is taken — hologram_exists.

Top shops, not players#

The shop_sales board ranks chest and NPC shops by totalRevenueCents, not player UUIDs.

/leaderboard shop_sales
/leaderboard shop_sales gui

In chat, lines use the shipped entryFormat ({rankColor}{medal} #{rank} …). Shops with zero revenue are skipped. exemptPermission has no effect on this board — the cache has no player to check. In the GUI these entries have no head: the board's icon (minecraft:emerald) or paper is used. A chest shop's name in the ranking is ownerName (the owner's name), an NPC shop's is shopName.

Edge cases#

`/baltop` and `/leaderboard money` are two different caches#

/baltop (and aliases) do not go through LeaderboardManager. Invalidation after /pay / /eco calls both the baltop cache and getBoard("money").invalidate() — the id is hardcoded as the string money. If you rename the economy board, {leaderboard_money:…} and invalidation diverge: /baltop updates immediately, while a sidebar with {leaderboard_<newid>:1:name} waits for that board's refreshInterval (60 s by default).

What to do: Do not change the money id if placeholders reference it. For a new economy board with another id, wait out the interval after paying money or run /leaderboard reload (it re-registers boards and clears the cache).

First request and the building message#

With an empty or stale cache, getTop() starts an async rebuild and immediately returns the current snapshot. An empty result while isBuilding()==true prints "building", not "empty". While a rebuild runs, "refreshing" may appear at the bottom of the page. vanilla_stat for offline players scans the whole stats directory — on a large world the first build is noticeable.

What to do: Repeat the command a second later. For heavy boards raise refreshInterval (shipped: 300 s for playtime, 120 for shop_sales).

`format` has almost no effect#

The field is read by the loader but does not make it into LeaderboardDefinition. It is used once: for vanilla_stat with format: "time" ticks are converted to Xh Ym (playtime). economy and shop_sales always print currency via EconomyManager. custom via CustomStatProvider.forBoard is always String.valueOf(long) — even if the JSON says currency.

What to do: Do not expect a currency symbol on a custom board. Time works only with vanilla_stat + format: "time".

vanilla_stat pulls everyone with a stats file into the ranking#

Online players come from ServerPlayer.getStats(). Offline — every *.json in <world>/stats/. A value of 0 is still put into the map, unlike shop_sales, where zero revenue is dropped. exemptPermission excludes the player entirely (PermissionAPI check by UUID). An invalid stat logs a WARN and skips only that board; the server does not crash.

What to do: Grant exemptPermission to service accounts. Remember: minecraft.mined:minecraft.diamond_ore does not count deepslate; for both types you need two boards or a separate custom counter.

`entryFormat` changes chat; the GUI draws its own#

Chat (/leaderboard <board>) substitutes {rank} {name} {value} {medal} {rankColor} and runs the line through RichTextFormatter. The GUI always sets the item name to #<rank> <name> and lore with the value — entryFormat is not passed there. GUI icon: a head if the entry has a UUID; otherwise the board's icon or minecraft:paper. Medals in the placeholders {leaderboard_<board>:<rank>:medal|rankcolor} work wherever placeholders are resolved, without entryFormat.

What to do: Style chat with entryFormat/headerFormat. The GUI has no other template.

A placeholder without a viewer yields a raw token#

LeaderboardPlaceholderExpansion returns null when player == null — PlaceholderAPI leaves {leaderboard_…} as is. A hologram resolves placeholders from the nearest player; if nobody is in the dimension, the token is not expanded on the refresh tick. A rank outside the list is an empty string, not an error. An unknown field (:foo) is null and the token stays.

What to do: Place the hologram where players actually are, or raise the hologram's refreshInterval while someone is nearby (/hologram setrefresh).

`/leaderboard reload` does not touch other mods' boards#

clearConfigManagedBoards() removes only ids that came from leaderboard.json. Boards from LeaderboardAPI.registerBoard without the configManaged flag survive reload. But they are not re-registered from the file: if another mod registers a board only at startup, reload keeps it; if it expected a repeat call, there is none. A disabled (enabled: false) JSON entry is simply not registered.

What to do: External boards are documented by that mod's author. Your own — only leaderboard.json + reload.

`admin delete` wipes the points#

deleteCustomBoard removes the entry from JSON, unregisters it and calls CUSTOM_STATS.clearBoard — all <id>:<uuid> keys in leaderboard_custom are deleted. Economy/vanilla boards cannot be deleted by command: edit the file. admin create with an id already in memory refuses; if the file had a disabled entry with the same name, create overwrites it with a custom definition.

What to do: Before delete, copy the collection's JSON if you still need the event points. Disable vanilla boards with enabled: false, not delete.

Known issues#

The GUI does not use `entryFormat`, although the comment in leaderboard.json promises it#

The shipped comment and LeaderboardSystem.md say entryFormat controls both chat and GUI item names. LeaderboardMenu.buildEntryStack always builds Component.literal("#" + rank + " " + name) and never reads definition.entryFormat(). On the shop_sales board, chat has medals and colours; the chest does not.

What to do: Treat /leaderboard <board> chat as the only place the template works. The GUI shows only rank, name and the value in lore.

Economy invalidation is tied to the `money` id#

BaltopCommand.invalidateCache() invalidates LeaderboardManager.getBoard("money") after /pay and /eco. An economy board with another id is not found by this call. /baltop itself still updates.

What to do: Keep the money id, or accept the delay until refreshInterval / a manual /leaderboard reload.

Configuration#

leaderboard.json#

KeyDefaultDescription
leaderboard
leaderboard.boards
leaderboard.boards.higherIsBettertrue, "exemptPermission": "neoessentials.economy.baltop.exempt"—
leaderboard.boards.enabledtrue }—
leaderboard.boards.displayName"Player Kills", "format": "integer", "higherIsBetter": true—
leaderboard.boards.exemptPermission"neoessentials.leaderboard.kills.exempt", "enabled": true }—
leaderboard.boards.displayName"Mob Kills", "format": "integer", "higherIsBetter": true—
leaderboard.boards.exemptPermission"neoessentials.leaderboard.mob_kills.exempt", "enabled": true }—
leaderboard.boards.displayName"Playtime", "format": "time", "higherIsBetter": true—
leaderboard.boards.exemptPermission"neoessentials.leaderboard.playtime.exempt", "refreshInterval": 300—
leaderboard.boards.enabledtrue }—
leaderboard.boards.displayName"Diamond Ore Mined", "format": "integer", "higherIsBetter": true, "enabled": false }—
leaderboard.boards.displayName"Zombie Kills", "format": "integer", "higherIsBetter": true, "enabled": false }—
leaderboard.boards.displayName"Distance Walked (cm)", "format": "integer", "higherIsBetter": true, "enabled": false }—
leaderboard.boards.displayName"Deaths", "format": "integer", "higherIsBetter": true, "enabled": false }—
leaderboard.boards.higherIsBettertrue, "enabled": false }—
leaderboard.boards.higherIsBettertrue, "refreshInterval": 120, "icon": "minecraft:emerald"—
leaderboard.boards.entryFormat"{rankColor}{medal} #{rank} &f{name} &8- &a{value}", "enabled": true }—

Permissions 5#

NodeIn registryWhat it grants
neoessentials.leaderboard.adminregistry: noManage leaderboards: reload, create/delete custom boards, set values
neoessentials.leaderboard.kills.exemptregistry: noExcluded from the kills leaderboard
neoessentials.leaderboard.mob_kills.exemptregistry: noExcluded from the mob kills leaderboard
neoessentials.leaderboard.playtime.exemptregistry: noExcluded from the playtime leaderboard
neoessentials.leaderboard.viewregistry: yesView leaderboards (/leaderboard, /lb)

Detailed guide#

The section below is the project's original documentation (docs/Wiki/LeaderboardSystem.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/leaderboard.json
Reload in-game: /leaderboard reload
Commands: /leaderboard (alias /lb) · Admin permission: neoessentials.leaderboard.admin
Introduced: v1.0.6 (shop boards, styling & GUI added in a later v1.0.6 update)


Overview#

A generalized, config-driven ranked-stat system. /baltop still works exactly as before and is untouched — the leaderboard system is a superset built alongside it, not a replacement.

Feature Details
Config-driven boards Register any vanilla-tracked per-player stat as a board just by adding a leaderboard.json entry — no code
Economy boards Ranks by balance (same data /baltop uses)
Vanilla stat boards Blocks mined by type, mobs killed by type, distance traveled, deaths, playtime, ... — anything vanilla's own /scoreboard objectives add criteria grammar can reference
Custom boards Point totals nothing in Minecraft tracks — admin-settable, dashboard-settable, or fed by another mod
Shop-sales board Ranks shops (sign/chest and NPC) by total revenue, shown by shop/owner name — the first board that isn't ranking players at all, see Non-Player Boards
Per-board styling Custom chat/GUI line templates, per-rank medal/color, and a per-board GUI icon — see Styling
GUI viewer /leaderboard <board> gui — a paginated chest-GUI browser, real player heads for player entries
LeaderboardAPI One-line board registration for other mods, mirroring PlaceholderAPI
Placeholders {leaderboard_<board>:<rank>:name\|value\|medal\|rankcolor} — usable in chat, tablist, the sidebar scoreboard, or holograms
Hologram generator /leaderboard hologram create <board> <id> [lines]
Dashboard endpoint /api/leaderboard

Why Vanilla Stats Need No New Tracking#

Minecraft already tracks dozens of per-player statistics on its own — kills, deaths, playtime, blocks mined (per block type), mobs killed (per entity type), distance traveled by every movement type, damage dealt/taken, items crafted/used/broken, and more — persisted to <world>/stats/<uuid>.json whether the player is online or not. The vanilla_stat board type reads this directly:

  • Online players — the live in-memory value via ServerPlayer.getStats(), no disk I/O.
  • Offline players — read straight from their stats JSON file.

Zero new event listeners, zero new storage. Any stat vanilla already knows about is a config entry away from being a leaderboard.


Configuration#

Full leaderboard.json structure#

{
  "_configVersion": 3,
  "leaderboard": {
    "boards": [
      { "id": "money", "type": "economy", "displayName": "Balance", "format": "currency",
        "higherIsBetter": true, "exemptPermission": "neoessentials.economy.baltop.exempt",
        "enabled": true },

      { "id": "kills", "type": "vanilla_stat", "stat": "minecraft.custom:minecraft.player_kills",
        "displayName": "Player Kills", "format": "integer", "higherIsBetter": true,
        "exemptPermission": "neoessentials.leaderboard.kills.exempt", "enabled": true },

      { "id": "mob_kills", "type": "vanilla_stat", "stat": "minecraft.custom:minecraft.mob_kills",
        "displayName": "Mob Kills", "format": "integer", "higherIsBetter": true, "enabled": true },

      { "id": "playtime", "type": "vanilla_stat", "stat": "minecraft.custom:minecraft.play_time",
        "displayName": "Playtime", "format": "time", "higherIsBetter": true,
        "refreshInterval": 300, "enabled": true },

      { "id": "diamonds_mined", "type": "vanilla_stat", "stat": "minecraft.mined:minecraft.diamond_ore",
        "displayName": "Diamond Ore Mined", "format": "integer", "higherIsBetter": true, "enabled": false },

      { "id": "event_points", "type": "custom", "displayName": "Event Points", "format": "integer",
        "higherIsBetter": true, "enabled": false },

      { "id": "shop_sales", "type": "shop_sales", "displayName": "Top Shops", "format": "currency",
        "higherIsBetter": true, "refreshInterval": 120, "icon": "minecraft:emerald",
        "entryFormat": "{rankColor}{medal} #{rank} &f{name} &8- &a{value}", "enabled": true }
    ]
  }
}

Board Fields#

Field Type Description
id string Used in /leaderboard <id>, /lb <id>, and placeholders
type string "economy" | "vanilla_stat" | "custom" | "shop_sales"
displayName string Shown in the board's header
format string "integer" | "time" | "currency"
higherIsBetter boolean true for "top" boards; false ranks lowest-first
exemptPermission string Players with this permission are excluded from the ranking (player-keyed boards only — ignored on shop_sales, which has no player to check)
refreshInterval integer Seconds a cached ranking is served before rebuilding on next access. Default 60 if omitted; cheap boards can go lower, expensive ones (offline-stats scans, the shop board) may want it higher
entryFormat string Optional custom line template for /leaderboard's chat output and the GUI's item names — see Styling
headerFormat string Optional custom header-line template — same section
icon string Item id (e.g. "minecraft:emerald") used as the GUI icon for entries with no player (e.g. a shop). Omit for minecraft:paper. Player entries always show their real head regardless
enabled boolean false hides the board without deleting its definition
stat string vanilla_stat only — see below

vanilla_stat Criteria Strings#

Uses vanilla's own scoreboard-objective criteria grammar — the exact same string /scoreboard objectives add <name> <criteria> accepts in-game:

<stat type>.<type path>:<value namespace>.<value path>
Category Example Meaning
minecraft.custom minecraft.custom:minecraft.player_kills Kills, deaths, playtime, distance, jumps, etc.
minecraft.mined minecraft.mined:minecraft.diamond_ore Blocks mined, by block id
minecraft.killed minecraft.killed:minecraft.zombie Mobs killed, by entity type id
minecraft.crafted / minecraft.used / minecraft.broken / minecraft.picked_up / minecraft.dropped minecraft.crafted:minecraft.diamond_pickaxe Item events, by item id

Look up exact value ids the same way you would for a vanilla /scoreboard objective — this mod doesn't invent its own stat names. A stat string that doesn't resolve to a real per-player stat logs a clear warning and that one board is skipped — it can't break server startup.

Custom Boards#

Point totals nothing in Minecraft tracks. Persisted through a single shared DataStore collection (leaderboard_custom, records keyed "<boardId>:<uuid>") — same single-collection, write-immediately pattern as PayToggleManager.


Non-Player Boards (shop_sales)#

Every board type above ranks players — the entry's identity is a UUID, and its display name comes from a Mojang profile lookup on that UUID. shop_sales is the first board that doesn't: it ranks shops (sign/chest shops and NPC shops) by total revenue, shown by the shop's own name, not a player's.

  • Sign/chest shops are keyed by their shopId (or a position-derived key if a shop predates that field); NPC shops by their own shopId. Neither goes through a Mojang lookup.
  • Revenue is tracked per-shop (totalRevenueCents on both shop data types), incremented on every buy/sell — this data didn't exist before this board was added.
  • Shops with zero sales are omitted from the ranking rather than showing a $0.00 entry.
  • No exemptPermission applies (there's no player to check a permission against).

This is built on a general extension point — NamedStatProvider — so any future non-player ranking (factions, guilds, anything with a stable id and its own name) can be added the same way without touching the core ranking/caching engine. See LeaderboardAPI below for how a mod would use it.


Styling: Templates, Medals & the GUI Viewer#

Three independent ways to customize how a board looks, all opt-in — every existing board keeps its original plain look until you add these fields.

Per-rank medals and colors (works everywhere placeholders do)#

Two new placeholder fields, available on every board with no config needed:

{leaderboard_<board>:<rank>:medal}      → 🥇 / 🥈 / 🥉 on ranks 1-3, empty string otherwise
{leaderboard_<board>:<rank>:rankcolor}  → a gold/silver/bronze color tag on ranks 1-3, empty otherwise

Use these in a hologram line, a sidebar scoreboard line, or a tablist line exactly like :name/:value — e.g. {leaderboard_money:1:rankcolor}{leaderboard_money:1:medal} {leaderboard_money:1:name}.

Per-board line templates (entryFormat / headerFormat)#

Set entryFormat (and optionally headerFormat) on a board in leaderboard.json to replace its default plain line — this affects both /leaderboard's chat output and the GUI's item names. Tokens:

Token Where Meaning
{rank} entry The rank number
{name} entry Entry's display name
{value} entry Formatted value (currency/time/integer per format)
{medal} entry Same as the :medal placeholder above
{rankColor} entry Same as the :rankcolor placeholder above
{displayName} header The board's displayName
{page} / {totalPages} header Current/total page number
{age} header Seconds since the ranking was last refreshed

Templates support the same &-codes, hex, and gradient tags as tablist/scoreboard/hologram lines — no new syntax to learn. A board with no entryFormat set keeps rendering through the original lang-file line exactly as before. Example (from the shipped shop_sales board):

"entryFormat": "{rankColor}{medal} #{rank} &f{name} &8- &a{value}"

GUI viewer#

/leaderboard <board> gui

Opens a paginated chest GUI for that board — 45 entries per page, prev/close/next buttons. Player entries show the player's real head (resolved from their UUID, same technique as the /skull command); non-player entries (shops) show the board's configured icon item, or minecraft:paper if none is set. View-only — editing still goes through /leaderboard admin set|add|reset for custom boards.


Commands#

Command Permission Description
/leaderboard / /lb neoessentials.leaderboard.view List registered boards
/leaderboard <board> [page] view Show a page of rankings
/leaderboard <board> gui view Open the paginated chest-GUI viewer (see Styling)
/leaderboard reload neoessentials.leaderboard.admin Reload leaderboard.json
/leaderboard admin set <board> <player> <value> admin Set a custom board's value
/leaderboard admin add <board> <player> <delta> admin Add to a custom board's value
/leaderboard admin reset <board> <player> admin Reset a custom board's value to 0
/leaderboard admin create <id> <displayName> admin Define a new custom board, persisted to leaderboard.json
/leaderboard admin delete <board> admin Delete a custom board
/leaderboard hologram create <board> <id> [lines] admin Generate a ranked leaderboard hologram (see Holograms below)

set/add/reset/delete only work on type: "custom" boards — economy/vanilla_stat boards are config-file-only (an admin should hand-verify a stat key in the file, not free-type one into a chat argument).

/baltop, /balancetop, /btop remain registered and fully independent — they don't route through this system at all, so nothing about existing setups changes.


Placeholders#

{leaderboard_<board>:<rank>:name|value|medal|rankcolor}, e.g.:

{leaderboard_money:1:name}       → richest player's name
{leaderboard_money:1:value}      → their formatted balance
{leaderboard_kills:3:name}       → 3rd-place player kills
{leaderboard_shop_sales:1:name}  → best-selling shop's name
{leaderboard_money:1:medal}      → 🥇 (empty string past rank 3)
{leaderboard_money:1:rankcolor}  → a gold color tag (empty string past rank 3)

Works anywhere the placeholder system is resolved: chat, Tablist, Scoreboard, and holograms. An out-of-range rank resolves to an empty string rather than an error.


Holograms#

/leaderboard hologram create <board> <id> [lines] (default lines = 10, max 15) spawns a hologram at your position with a title line plus N ranked lines using {leaderboard_<board>:<rank>:name} / :value}. This is a pure convenience generator — no new rendering mechanism — the Hologram System already resolves {placeholder} tokens live on its own refresh timer, so the generated lines keep updating on their own exactly like any other hologram placeholder.

/leaderboard hologram create money top_balances 5
/hologram setrefresh top_balances 30

One caveat: a hologram is a single shared entity, not per-player packets — its placeholders resolve using whichever player happens to be nearest at refresh time (harmless for leaderboard placeholders specifically, since ranks are global, not per-viewer). If nobody is anywhere near the hologram's dimension at a refresh tick, the token can't resolve that tick and shows literally until someone's nearby again.


LeaderboardAPI — For Mod Developers#

Same one-line integration story as PlaceholderAPI.

import com.zerog.neoessentials.leaderboard.LeaderboardAPI;
import com.zerog.neoessentials.leaderboard.LeaderboardDefinition;
import com.zerog.neoessentials.leaderboard.StatProvider;

// Call during your mod's own init/server-starting hook:
LeaderboardAPI.registerBoard(
    new LeaderboardDefinition("mymod_wins", "Arena Wins", "mymod.leaderboard.exempt", true),
    (server) -> myWinsMap()   // StatProvider: Map<UUID, Number> getAllValues(MinecraftServer)
);

StatProvider is a two-method functional-shaped interface for player-keyed boards:

public interface StatProvider {
    Map<UUID, Number> getAllValues(MinecraftServer server);
    String formatValue(Number value);
}

For a board that isn't ranking players (the same problem shop_sales solves — see Non-Player Boards), implement NamedStatProvider instead:

public interface NamedStatProvider extends StatProvider {
    record NamedEntry(String displayName, Number value) {}
    Map<String, NamedEntry> getAllNamedValues(MinecraftServer server); // keyed by YOUR own stable id
}

LeaderboardDefinition also has a refreshIntervalSeconds field (default 60) and optional entryFormat/headerFormat/icon styling fields — pass them via the full constructor, or omit for defaults:

LeaderboardAPI.registerBoard(
    new LeaderboardDefinition("mymod_wins", "Arena Wins", "mymod.leaderboard.exempt", true, 300),
    (server) -> myWinsMap());
LeaderboardAPI.unregisterBoard("mymod_wins");
LeaderboardCache cache = LeaderboardAPI.getBoard("mymod_wins"); // read current standings

Requirements & caveats: - Requires NeoEssentials as a compile/runtime dependency, same as any cross-mod NeoForge API. - Boards registered this way are in-memory only — not written to leaderboard.json, and won't survive a /leaderboard reload unless you re-register on your own startup hook (same contract PlaceholderExpansion already has). - Once registered, the board immediately works everywhere: /leaderboard//lb, /leaderboard <board> gui, the {leaderboard_...} placeholder, and /leaderboard hologram create.


Web Dashboard API#

/api/leaderboard (auth required for reads, auth-admin for writes):

Method Path Description
GET /api/leaderboard List registered board ids + display names
GET /api/leaderboard/{board}?page=N Paginated entries for one board
POST /api/leaderboard/boards Create a custom board — { id, displayName }
PUT /api/leaderboard/boards/{id}/value Set a custom board's value — { player, value }
DELETE /api/leaderboard/boards/{id} Delete a custom board

Write endpoints only accept type: "custom" boards — same restriction as the in-game admin subcommands.


Architecture Notes (for maintainers)#

  • LeaderboardManager — the board registry (com.zerog.neoessentials.leaderboard). Tracks which board ids came from leaderboard.json (configManaged) so a /leaderboard reload never wipes out boards another mod registered via LeaderboardAPI.
  • LeaderboardCache — per-board async cache: 60s staleness window, build-in-flight/ rebuild-queued handling for invalidations mid-build — the generalized lift of BaltopCommand's original static-field caching into one instance per board.
  • LeaderboardConfigLoader — reads leaderboard.json, builds the right StatProvider per entry, and tracks which board ids are actually type: "custom" (distinct from merely config-managed — an economy/vanilla_stat board is config-managed too, but isn't custom-editable).
  • NamedStatProvider — sibling interface to StatProvider for non-player boards; LeaderboardCache checks for it before falling back to the UUID/profile-lookup path. ShopSalesStatProvider (leaderboard/adapters/) is the only current implementation.
  • LeaderboardStyle — the single place rank→medal/color logic lives, shared by the placeholder expansion, /leaderboard's template rendering, and the GUI.
  • A config-version note: leaderboard.json's array-merge (adding a new default board like shop_sales to an already-upgraded install) only adds boards missing by id — it can't retroactively add new fields (entryFormat/icon/etc.) to a board an install already has on disk. New installs and newly-added board ids get every field; existing boards on upgraded installs keep their on-disk shape until an admin edits the file by hand.

Back to Wiki Home