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); }

Registered by both modules on onEnable:

  • paper: AstralPaperAPI.registerService(LeaderboardService.class, this.leaderboards) — backed by LeaderboardServiceImpl, whose update delegates to EntriesService.update (an overwrite, wired through UpdatePlayerEntryPacket, same as /leaderboard set).

  • master: MageHicAPI.services().register(LeaderboardService.class, this.entries) — backed by EntryService, whose update reuses the ranking direction already known for that leaderboard id (sortFor(leaderboardId)) and calls save directly (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 always overwrites the score across every current period bucket — there is no shared-service increment. To add a delta instead of setting an absolute value, a plugin must depend on AstralLeaderboards directly and call AstralLeaderboards.get().entries().increment(...) (see below); the built-in trackers use this path.

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:

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 or, from within the plugin, plugin.entries().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: 25 July 2026