API
Read arenas, drive joins and leaves, and pull kill counters and player toggles out of ExyliaFFA.
FfaService is everything another plugin can read or drive in ExyliaFFA: the arenas and who is in
them, the joins and kicks a command would run, the stored counters behind a leaderboard, and the
toggles a player owns. One lookup gets you all of it.
ExyliaAPI.get(FfaService.class).ifPresent(ffa ->
ffa.arenaOf(player.getUniqueId())
.ifPresent(arena -> player.sendMessage("Fighting in " + arena)));The artifact, the repository and the plugin.yml line are the same for every Exylia plugin and live
on the public API page.
The arena, the statistics and the per-player toggles are one plugin from a consumer's point of view — a stats menu wants all three — so they are one interface with three sections rather than three services to look up separately.
Everything that returns a value reads a cache and is safe from a menu redraw or a placeholder, with
the two documented exceptions that return a CompletableFuture and go to the database. Everything
returning void runs the same flow the player's own command runs — permission checks, the messages
they see, sometimes a menu they have to answer — so call those on the main thread and no more often
than a player could trigger them.
Arenas and state
| Method | What it does |
|---|---|
Optional<FfaArena> arena(String arenaId) | One arena by id. Empty when no arena has that id. |
List<FfaArena> arenas() | Every loaded arena, disabled ones included — a menu wants to draw those greyed out rather than not at all. |
boolean isInFfa(UUID player) | Whether they are in any arena, alive or spectating. |
Optional<String> arenaOf(UUID player) | The arena id they are in. Empty when they are in none. |
boolean isAlive(UUID player) | Whether they are fighting rather than watching. |
boolean isSpectating(UUID player) | Whether they are watching rather than fighting. |
List<UUID> alivePlayers(String arenaId) | Everybody alive in an arena. Empty when the arena does not exist. |
List<UUID> spectators(String arenaId) | Everybody spectating an arena. Empty when the arena does not exist. |
int aliveCount(String arenaId) | How many are alive. 0 when the arena does not exist. |
int playerCount(String arenaId) | How many it holds, fighting and watching together. |
boolean isFull(String arenaId) | Whether it has reached its player limit. |
int secondsUntilRegeneration(String arenaId) | Seconds until it rebuilds itself. 0 when it never does, or does not exist. |
boolean isProtected(Location location) | Whether a location is inside any arena's protected spawn zone. |
boolean isAvailable(Player player) | Whether nothing else on the server currently has this player. |
Optional<String> unavailableReason(Player player) | Why they cannot be taken, in words meant for a log. Empty when they are free. |
isProtected is the question a combat or block listener wants. Ask it here rather than rebuilding
the test from an arena's configuration, and a shape or a radius an administrator changed is answered
correctly without your code knowing the zone geometry exists.
isAvailable is one question to the server-wide session registry, so a mode added later — an event,
a duel, the sandbox — is covered without the method changing. A true is not a reservation: the join
itself takes the claim atomically and is what actually decides.
isInFfa, arenaOf, isAlive, isSpectating and stats take a UUID, but the state behind them
lives in the plugin's per-session managers, which only know a live Player. Each resolves the id
through Bukkit.getPlayer(uuid) and answers false or empty when that returns nothing.
For arena state this is the honest answer — an offline player is in no arena. For counters it is not:
an offline player still has rows. Building a leaderboard or a lookup for somebody who is not online
means statsOf(UUID), which goes to the database, or leaderboard(...), which is already a database
query.
Actions
Each of these runs the same flow the player's own command runs, including the permission checks and the messages they see. They report nothing back: the player is told what happened, and a caller that needs to know should read the state afterwards.
| Method | What it does |
|---|---|
void join(Player player, String arenaId) | The player's own join. When the arena offers more than one spawn or more than one selectable kit, they are shown the menu that asks, and the join finishes when they answer. |
void join(Player player, String arenaId, String spawnId, String kitId) | The same join with both choices already made, so no menu appears whatever the arena's configuration. An unknown spawn or kit id falls back to the arena's own selection rather than failing the join. |
void leave(Player player) | Takes them out of the arena they are in. |
void spectate(Player player, String arenaId) | Puts them into an arena as a spectator. |
void respawn(Player player) | Brings a spectating player back into the fight. Does nothing when they are not spectating, which is what their own respawn button answers. |
void kick(Player kicker, Player target) | Removes a player on somebody else's authority. The kicker's permissions are checked exactly as they are for their own command, so this cannot bypass them. |
void invite(Player inviter, Player target) | Invites a player to the inviter's arena. |
Statistics
Counters are kept per arena. FfaService.GLOBAL_ARENA — the string global — is the arena id
holding a player's totals across every arena: the plugin writes that second row on every counter
change, so a server-wide leaderboard is the same query as an arena one rather than a sum computed at
read time.
| Method | What it does |
|---|---|
Optional<FfaStats> stats(UUID player, String arenaId) | One arena's counters from the cache. Empty when nothing is cached for that pair, which includes every offline player. |
CompletableFuture<List<FfaStats>> statsOf(UUID player) | Every row a player has, one per arena plus their totals. Goes to the database, so it answers for offline players; the future completes on a database thread. |
CompletableFuture<List<FfaStats>> leaderboard(String arenaId, FfaStatistic statistic) | The top players of an arena by one counter, best first. Queries unless the plugin has a warm copy, and completes on a database thread either way. |
Optional<List<FfaStats>> cachedLeaderboard(String arenaId, FfaStatistic statistic) | The leaderboard already in memory, never querying. Empty means it has not been built yet, not that the arena has no players. |
int currentStreak(UUID player) | Their live kill streak, the counter the streak rewards run off. 0 when they have none. |
void resetStats(UUID player, String arenaId) | Clears one arena's counters. Works for an offline player, and leaves the row in place with every counter at zero rather than deleting it. |
currentStreak is not FfaStats.currentStreak(). That one is the stored value for one arena; this
one is what the player has going right now, and a death or a leave resets it.
For a placeholder or a menu redraw prefer cachedLeaderboard — the distinction between "not built
yet" and "no players" is exactly what lets it print a placeholder value instead of blocking a tick on
a query.
Player settings
| Method | What it does |
|---|---|
boolean isSettingEnabled(UUID player, FfaSetting setting) | The player's own answer for one setting. |
boolean isSettingAvailable(FfaSetting setting) | Whether an administrator has left the feature on for the whole server. |
boolean toggleSetting(Player player, FfaSetting setting) | Flips it, and returns the value it now has. |
The two reads are separate on purpose. A player who has never opened the settings menu has every setting on — an absent choice is a default, not an off — and a setting an administrator switched off is not a choice the player can make, whatever their own answer says. Anything deciding whether to actually show a feature has to ask both; a settings menu needs them apart, because it draws the player's answer on a row it has greyed out.
Types
FfaArena — a snapshot of the stored row: id, displayName, maxPlayers, kitMode,
enabled, regenerationSeconds (0 when it never regenerates) and permission (empty when anybody
may join). Anything that changes while players fight is a method on the service instead, so reading
an arena stays a cache hit. The region, protected-zone shape and rule set are deliberately absent —
they are the plugin's own geometry types, and isProtected(Location) is the question they answer.
FfaStats — player, playerName, arenaId, kills, deaths, currentStreak,
bestStreak, assists, damageDealt, playTimeSeconds, aliveTimeSeconds, kdr and
killsPerMinute. Two things are not obvious from the names:
arenaIdis part of the record rather than implied. A player has as many of these as they have arenas played, plus theirGLOBAL_ARENArow.playerNameisnullwhen the player has never been online since the row was written, andkdrandkillsPerMinuteare stored rather than derived because the database sorts leaderboards by them. They follow the plugin's rule everywhere: a player who has never died reports their kill count rather than infinity, so a leaderboard can sort it and a menu can print it.
FfaKitMode — how an arena hands out kits: SELECTABLE (the player picks, from the kits the
arena carries) or RANDOM (the arena picks one on every spawn).
FfaStatistic — what a leaderboard sorts by: KILLS, DEATHS, KDR, BEST_STREAK,
ASSISTS, DAMAGE_DEALT, PLAY_TIME, ALIVE_TIME, KILLS_PER_MINUTE. Each names a component of
FfaStats and each has a database index behind it, which is why sorting is a choice from this set
rather than an arbitrary field name.
FfaSetting — the toggles a player owns: SCOREBOARD, SPAWN_WAYPOINTS, COMBAT_ACTION_BAR,
LINKED_ACTION_BAR, COMBAT_MESSAGES, KILL_STREAK_NOTIFICATIONS, KILL_STREAK_BROADCASTS,
KILL_DEATH_BROADCASTS, GLOW_LINKED_PLAYER, SPECTATOR_ACTIONBAR_HINT.
What it does not expose
ExyliaFFA publishes no events.
Menu openers, the kit editor flow and the administrative writes behind /ffaadmin are left out on
purpose, as are the raw arena geometry and rule set. Invitations can be sent but not answered from
here: accepting and declining are the invited player's own choice, made from their own client.
If something you need is missing, ask on Discord — a method added to a service is a minor release.
Something missing on this page? Tell us on Discord