Farmstead Mods
EN

Лидерборды

Рейтинговые доски по ванильной статистике, экономике и собственным очкам, с GUI и голограммами.

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

Команды 1#

КомандаСинтаксисАлиасыПравоОписание
/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, ...)Алиас `/lb` регистрируется тем же деревом. Корень — neoessentials.leaderboard.view (консоль проходит); `reload` / `admin` / `hologram` — neoessentials.leaderboard.admin. `<board>`/`<id>`/`<player>` — word; `<displayName>` greedy; `<value>`/`<delta>` — long; `<page>` ≥ 1; в чате страница по 10 строк, GUI — 45. `gui` и `hologram create` только от игрока. set/add/reset/delete работают лишь на type `custom`. Отдельного `modules.leaderboardEnabled` и ключа `commands.leaderboard` в поставке нет — команда регистрируется всегда. `/baltop` эту систему не вызывает.

Примеры#

Посмотреть топ баланса в чате и в сундуке#

Проверить, что доска money из поставки жива и что GUI — тот же рейтинг, просто другой вид.

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

Голый /leaderboard печатает id зарегистрированных досок (у поставки как минимум money, kills, mob_kills, playtime, shop_sales; выключенные в JSON не регистрируются). /leaderboard money — первая страница по 10 строк, заголовок с номером страницы и возрастом кеша в секундах. Если кеш ещё строится, вместо «пусто» придёт сообщение building — подождите и повторите. gui открывает сундук 6×9: головы игроков, навигация на слотах 48–50. Из консоли gui не открывается.

Включить доску «алмазная руда» из поставки#

В leaderboard.json уже есть diamonds_mined с enabled: false. Включить её без правки Java.

/leaderboard reload
/leaderboard
/leaderboard diamonds_mined

Поставьте enabled: true у записи diamonds_mined и выполните reload (или /neoe reload — он тоже вызывает LeaderboardConfigLoader.load()). Доска появится в списке. stat — ванильная criteria-строка minecraft.mined:minecraft.diamond_ore: считается только обычная алмазная руда, не deepslate. Офлайн-игроки читаются из <world>/stats/<uuid>.json; у кого файл есть, тот попадает в таблицу даже с нулём.

Свои очки события#

Завести доску, которой нет в ванильной статистике, и начислить очки офлайн-игроку.

/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

Если id уже занят — ошибка board_exists. create дописывает объект type: custom в leaderboard.json и регистрирует доску сразу. set/add/reset резолвят UUID сначала среди онлайн, затем через profile cache — офлайн-ник с известным профилем проходит; подсказки автодополнения показывают только онлайн. Значения пишутся в коллекцию DataStore leaderboard_custom (ключ <boardId>:<uuid>) и сразу инвалидируют кеш этой доски. reset — это set … 0. Экономическую или vanilla_stat доску эти ветки отвергнут: not_custom_board.

Голограмма топа баланса#

Не набивать 10 строк /hologram addline руками — генератор ставит title + N плейсхолдеров {leaderboard_<board>:<rank>:name|value}.

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

Нужны модуль голограмм, игрок (не консоль) и свободный id. Голограмма спавнится у ваших ног +1.5 по Y, id приводится к нижнему регистру, refreshInterval генератора — 5 секунд. lines по умолчанию 10, максимум 15 (Brigadier 1–15). Это обычная голограмма: дальше ею управляет /hologram. Если модуль голограмм выключен — holograms_disabled. Если id занят — hologram_exists.

Топ магазинов, не игроков#

Доска shop_sales ранжирует сундучные и NPC-магазины по totalRevenueCents, а не UUID игроков.

/leaderboard shop_sales
/leaderboard shop_sales gui

В чате строки идут через entryFormat поставки ({rankColor}{medal} #{rank} …). Магазины с нулевой выручкой пропускаются. exemptPermission на эту доску не действует — в кеше нет игрока, которого проверять. В GUI у таких записей нет головы: берётся icon доски (minecraft:emerald) или бумага. Имя сундучного магазина в рейтинге — ownerName (ник владельца), у NPC — shopName.

Тонкие места#

`/baltop` и `/leaderboard money` — два разных кеша#

/baltop (и алиасы) не ходят в LeaderboardManager. Инвалидация после /pay / /eco вызывает и кеш baltop, и getBoard("money").invalidate() — id зашит строкой money. Если переименовать экономическую доску, {leaderboard_money:…} и инвалидация разъедутся: /baltop обновится сразу, сайдбар с {leaderboard_<новоеid>:1:name} будет ждать refreshInterval этой доски (по умолчанию 60 с).

Что делать: Не меняйте id money, если на него ссылаются плейсхолдеры. Для новой экономической доски с другим id после выдачи денег подождите интервал или дерните /leaderboard reload (он перерегистрирует доски и сбросит кеш).

Первый запрос и сообщение building#

getTop() при пустом или протухшем кеше запускает async-пересчёт и сразу возвращает текущий снимок. Пустая выдача при isBuilding()==true печатает «building», а не «empty». Пока пересчёт идёт, внизу страницы может появиться «refreshing». vanilla_stat на офлайн сканирует весь каталог stats — на большом мире первая сборка заметна.

Что делать: Повторите команду через секунду. Для тяжёлых досок поднимите refreshInterval (у playtime в поставке 300 с, у shop_sales 120).

`format` почти ни на что не влияет#

Поле читается в лоадере, но в LeaderboardDefinition не попадает. Оно используется один раз: для vanilla_stat при format: "time" включается перевод тиков в Xh Ym (playtime). economy и shop_sales всегда печатают валюту через EconomyManager. custom через CustomStatProvider.forBoard всегда String.valueOf(long) — даже если в JSON написано currency.

Что делать: Не ждите символа валюты на custom-доске. Время — только vanilla_stat + format: "time".

vanilla_stat тащит в рейтинг всех, у кого есть stats-файл#

Онлайн берутся из ServerPlayer.getStats(). Офлайн — каждый *.json в <world>/stats/. Значение 0 всё равно кладётся в карту, в отличие от shop_sales, где нулевая выручка отбрасывается. exemptPermission исключает игрока целиком (проверка PermissionAPI по UUID). Неверный stat логирует WARN и пропускает только эту доску, сервер не падает.

Что делать: Выдавайте exemptPermission служебным аккаунтам. Помните: minecraft.mined:minecraft.diamond_ore не считает deepslate; для обоих типов нужны две доски или отдельный custom-счётчик.

`entryFormat` меняет чат, GUI рисует своё#

Чат (/leaderboard <board>) подставляет {rank} {name} {value} {medal} {rankColor} и прогоняет строку через RichTextFormatter. GUI всегда ставит имя предмета #<rank> <name> и lore со значением — entryFormat туда не передаётся. Иконка GUI: голова, если у записи есть UUID; иначе icon доски или minecraft:paper. Медали в плейсхолдерах {leaderboard_<board>:<rank>:medal|rankcolor} работают где угодно резолвятся плейсхолдеры, без entryFormat.

Что делать: Оформляйте чат через entryFormat/headerFormat. В GUI другого шаблона нет.

Плейсхолдер без зрителя даёт пустой токен#

LeaderboardPlaceholderExpansion при player == null возвращает null — PlaceholderAPI оставляет {leaderboard_…} как есть. Голограмма резолвит плейсхолдеры от ближайшего игрока; если в измерении никого нет, на тике refresh токен не раскроется. Ранг вне списка — пустая строка, не ошибка. Неизвестный field (:foo) — null, токен остаётся.

Что делать: Ставьте голограмму там, где бывают игроки, или поднимите refreshInterval голограммы, когда кто-то рядом (/hologram setrefresh).

`/leaderboard reload` не трогает доски других модов#

clearConfigManagedBoards() снимает только id, пришедшие из leaderboard.json. Доски LeaderboardAPI.registerBoard без флага configManaged переживают reload. Но они сами не перерегистрируются из файла: если другой мод регистрирует доску только на старте, reload её оставит; если он ждал повторного вызова — его нет. Выключенная (enabled: false) запись из JSON просто не регистрируется.

Что делать: Внешние доски документируйте у автора того мода. Свои — только leaderboard.json + reload.

`admin delete` стирает очки#

deleteCustomBoard убирает запись из JSON, снимает регистрацию и вызывает CUSTOM_STATS.clearBoard — все ключи <id>:<uuid> в leaderboard_custom удаляются. Экономическую/ванильную доску удалить командой нельзя: правится файл. admin create с тем же id, что уже есть в памяти, отказывается; если в файле была выключенная одноимённая запись, create перезапишет её custom-определением.

Что делать: Перед delete скопируйте JSON коллекции, если очки события ещё нужны. Ванильные доски выключайте enabled: false, не delete.

Известные проблемы#

GUI не использует `entryFormat`, хотя комментарий в leaderboard.json обещает это#

Комментарий поставки и LeaderboardSystem.md пишут, что entryFormat задаёт и чат, и имена предметов в GUI. LeaderboardMenu.buildEntryStack всегда делает Component.literal("#" + rank + " " + name) и не читает definition.entryFormat(). На доске shop_sales в чате медали и цвета есть, в сундуке — нет.

Что делать: Ориентируйтесь на чат /leaderboard <board> как на единственное место, где шаблон работает. GUI — только ранг, имя и значение в lore.

Инвалидация экономики привязана к id `money`#

BaltopCommand.invalidateCache() после /pay и /eco инвалидирует LeaderboardManager.getBoard("money"). Другое id экономической доски этот вызов не найдёт. Сам /baltop при этом обновится.

Что делать: Оставляйте id money или смиритесь с задержкой до refreshInterval / ручным /leaderboard reload.

Конфигурация#

leaderboard.json#

КлючПо умолчаниюОписание
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 }—

Права 5#

УзелВ реестреЧто даёт
neoessentials.leaderboard.adminреестр: нетManage leaderboards: reload, create/delete custom boards, set values
neoessentials.leaderboard.kills.exemptреестр: нетExcluded from the kills leaderboard
neoessentials.leaderboard.mob_kills.exemptреестр: нетExcluded from the mob kills leaderboard
neoessentials.leaderboard.playtime.exemptреестр: нетExcluded from the playtime leaderboard
neoessentials.leaderboard.viewреестр: даView leaderboards (/leaderboard, /lb)

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

Раздел ниже — исходная документация проекта (docs/Wiki/LeaderboardSystem.md). Ссылки на другие страницы вики переписаны на этот сайт; текст самих руководств не редактируется, чтобы синк с апстримом не давал конфликтов.

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