Astral Realms Documentation Help

Developer API

AstralLeaderboards exposes writes to other plugins through a single shared-service interface registered in AstralCore's service registry — integrating plugins do not need a compile dependency on AstralLeaderboards itself.

LeaderboardService

public interface LeaderboardService extends AstralService { void update(UUID playerId, String leaderboardId, double value); void increment(UUID playerId, String leaderboardId, double delta); }

Registered by both modules on onEnable:

  • paper: AstralPaperAPI.registerService(LeaderboardService.class, this.leaderboards) — backed by LeaderboardServiceImpl, which delegates straight to EntriesService.update/EntriesService.increment, wired through UpdatePlayerEntryPacket/IncrementPlayerEntryPacket (the same paths as /leaderboard set and the built-in trackers). The packet carries the sort declared for that id in the local config.yml (DESC when the id is not declared there).

  • master: MageHicAPI.services().register(LeaderboardService.class, this.entries) — backed by EntryService, whose update and increment call the internal save/increment with a null sort, so the ranking direction already known for that leaderboard id is reused and not overwritten. No round trip, since it's already on the master.

From another paper plugin:

import com.astralrealms.core.paper.AstralPaperAPI; import com.astralrealms.core.service.impl.LeaderboardService; AstralPaperAPI.getService(LeaderboardService.class) .ifPresent(leaderboards -> leaderboards.update(player.getUniqueId(), "jobs-level-miner", level));

update overwrites the score across every current period bucket; increment adds delta to it instead. Both are reachable through the shared service, so neither needs a compile dependency on AstralLeaderboards.

Known consumers in the codebase: AstralEconomy (TransactionService) and AstralJobs (LeaderboardListener, pushes a job's level on level-up).

Direct plugin API (paper)

For code that already depends on the AstralLeaderboards artifact, the plugin instance exposes the same writes plus the reads and cache controls the shared service does not carry:

AstralLeaderboards plugin = AstralLeaderboards.get(); plugin.entries().update(playerId, "kills", 42.0); // overwrite plugin.entries().increment(playerId, "kills", 1.0); // add a delta plugin.leaderboards().cached("kills", LeaderboardPeriod.WEEKLY); // synchronous cached read (empty list on miss) plugin.leaderboards().get("kills", LeaderboardPeriod.WEEKLY); // CompletableFuture, loads on miss

Getter

Type

Use

plugin.configuration()

LeaderboardsConfiguration

Declared leaderboards, builtin-leaderboards map.

plugin.entries()

EntriesService

Per-player standings: update, increment, getCached, refresh, invalidate.

plugin.leaderboards()

LeaderboardServiceImpl

Per-leaderboard top-N reads: cached, get, refresh, invalidate.

plugin.playerData()

PlayerDataService

In-memory per-session accumulators for the built-in trackers (see below).

plugin.menus()

MenuContainer

The leaderboards-list menu loader.

Built-in trackers

PlayerDataListener accumulates three stats per online player in memory (PlayerData, a Map<BuiltinLeaderboard, Integer>) and flushes them to the network as a single increment call per stat on PlayerQuitEvent — not on every event, to avoid a network round trip per block break:

BuiltinLeaderboard

Bukkit event

Counts

Bypass check

BLOCKS

BlockBreakEvent (MONITOR, ignoreCancelled)

+1 per block broken.

leaderboards.bypass

KILLS

EntityDeathEvent (MONITOR, ignoreCancelled)

+1 per kill, only when getKiller() is a player.

leaderboards.bypass (checked on the killer)

FISHED

PlayerFishEvent (MONITOR, ignoreCancelled)

+1 only when getState() == CAUGHT_FISH.

leaderboards.bypass

On quit, each accumulated stat is flushed only if builtin-leaderboards in config.yml maps that BuiltinLeaderboard to a leaderboard id; otherwise it is dropped (logged as a skip, not an error). If the player disconnects without a PlayerQuitEvent firing normally (e.g. server crash), the in-memory accumulator for that session is lost — the built-in trackers are best-effort, not durable.

BuiltinLeaderboard

public enum BuiltinLeaderboard { BLOCKS, KILLS, FISHED }

Fixed set — adding a new automatically-tracked stat requires a code change (a new enum value, a new listener method, and a builtin-leaderboards entry), there is no registration hook.

Extending: custom leaderboards

No code is required to add a new leaderboard — declare a new entry under leaderboards in config.yml (see Configuration) and start writing to it via LeaderboardService.update/LeaderboardService.increment. The master learns the leaderboard's sort direction from the first write packet it receives and persists it, so no master-side configuration is needed either.

Last modified: 03 September 2026