Content generated with AI — it may contain mistakes.

Referencedev

API

Read what is running, what is configured and what a player has done, move players in and out of an event, and follow its lifecycle.

EventsService is how another plugin finds out whether a player is inside a minigame, what is running or startable right now, and how it puts somebody in or takes them out. Six Bukkit events report the life of every run as it happens.

ExyliaAPI.get(EventsService.class).ifPresent(events ->
    events.eventOf(player.getUniqueId())
          .ifPresent(event -> player.sendMessage("Playing " + event.displayName())));
Needs exylia-api v1.133.0

forceStart, openMenu and the lifecycle events arrived in v1.133.0. An older artifact has none of them.

Adding it to your project

The artifact, the repository and the plugin.yml line are the same for every Exylia plugin and live on the public API page.

Definitions and runs

An EventDefinition is what an admin set up; a GameEvent is one playing of it. Definitions outlive runs and are what statistics are filed under, so anything that persists — a leaderboard, a menu, a placeholder — keys on a definition id, while a join, a spectate or a force-end names the id of a run that exists right now.

Everything that returns a value reads from the plugin's caches and is safe to call from a menu redraw or a placeholder. Everything that acts moves players between worlds, saves and clears inventories and writes rows, so call those on the main thread and no more often than a player could trigger them.

Running events

MethodWhat it does
Optional<GameEvent> eventOf(UUID)The event a player is in, or empty when they are in none.
Optional<GameEvent> eventById(String eventId)One running event by the id of the run, or empty when nothing is running under it.
List<GameEvent> runningEvents()Every event running right now, in no particular order.
List<GameEvent> joinableEvents()Every event a player could still be let into.
boolean isInEvent(UUID)Whether any event has them, playing or spectating.
boolean isPlaying(UUID)Whether they are a participant who has not been eliminated.
boolean isSpectating(UUID)Whether they are watching a running event.

eventOf and isInEvent cover spectating as well as playing: a player watching an event is in it as far as everything else on the server is concerned, which is the question a chat, a scoreboard or a teleport plugin is really asking. isSpectating covers being eliminated too — an eliminated player stays in the event, watching the rest of it, so the two are the same thing from outside.

joinableEvents() filters on state only. An event in that list may still be full, so a menu that offers them should check GameEvent.isFull() before it promises anything.

Definitions

MethodWhat it does
Optional<EventDefinition> definition(String configId)One configured event, or empty when nothing is configured under that id.
List<EventDefinition> definitions()Every configured event, disabled ones included.
List<EventDefinition> startableDefinitions()Every event that could be started right now: enabled, fully set up, and not already running.

startableDefinitions() is the list a "start an event" menu should offer, because the other two would offer entries start(String) would refuse.

Statistics

MethodWhat it does
Optional<EventStats> stats(UUID)A player's counters across every event, or empty while the first read is in flight.
Optional<EventStats> eventStats(UUID, String configId)The same counters within one configured event, empty for the same reason.

Both are read from the cache. A player nothing has asked about yet answers empty and the read is started, so a placeholder drawn on the main thread never waits on the database and the next draw has the number.

Actions

Each of these runs the same flow the player's own command runs, including the permission checks, the claim on the player and the messages they see. A false is a refusal that has already been explained to them.

MethodWhat it does
boolean join(Player, String eventId)Puts a player into a running event. true when they are in it afterwards.
boolean leave(Player)Takes a player out of whatever event has them, playing or spectating.
boolean spectate(Player, String eventId)Puts a player into a running event as a spectator.
Optional<GameEvent> start(String configId)Starts a new run of a configured event, or empty when it was refused.
boolean forceEnd(String eventId)Ends a running event now, without a winner.
boolean forceStart(String eventId)Begins play in a run now, without waiting out its countdown. true when play began.

start is refused when the definition is disabled, incompletely set up, or already running: one configuration hosts one run at a time, because the arena is part of the configuration.

forceEnd is the administrative stop — players are given their inventories back and sent home, and nothing is paid out. Use it for a moderation call or a shutdown, not to finish a game.

forceStart is the administrative start, for a host who does not want to wait: the minimum player count is not checked, only that somebody is in the run. It is refused when the run is already being played or ending, or when nobody is in it yet.

MethodWhat it does
void openMenu(Player)Opens the events menu for a player, the one /events opens.

The screen a lobby NPC or a hotbar item wants: the running events, a way into them and the player's own record, drawn from the server's own menu files. Call it on the thread that owns the player, as an interaction handler already is.

Lifecycle events

Six Bukkit events in net.exylia.lib.api.events.event follow a run from its start to its end. Each carries a GameEvent snapshot or an EventDefinition, never the live game. Two can be cancelled, and both fire before anything has been taken from anybody.

EventWhenCancellable
GameStartEventA run of a configuration is about to open. getDefinition(), getStarter().Yes
GamePlayerJoinEventA player is about to join a run to play it. getPlayer(), getEvent().Yes
GameBeginEventThe countdown is over: the players are in the arena and the clock is running. getEvent().No
GamePlayerEliminateEventA player is out of the game and stays in the event to watch. getPlayer(), getEvent().No
GamePlayerLeaveEventA player has left the run, playing or watching. getPlayer(), getEvent().No
GameEndEventThe run is over. getEvent(), getWinners(), getReason().No
@EventHandler
public void onStart(GameStartEvent event) {
    if (maintenance) {
        event.setCancelled(true);
        event.getStarter().ifPresent(p -> p.sendMessage("Events are paused for maintenance."));
    }
}

GameStartEvent fires once every check has passed — enabled, set up, this server's to play, not already running, within the limits — and before the run exists. Every start comes through it: a command, the admin menu, the timetable, the random scheduler, a request from another server and start(String) alike, which makes it the one place to hold events back. A cancelled start leaves no run, no broadcast, no slot taken in the limits and no cooldown. getStarter() is empty for a schedule or an API call. The plugin cannot know why you refused, so tell the starter yourself.

GamePlayerJoinEvent fires once the run has room and the player is free, in an allowed world and signed up where the event asks for it, before their claim is taken, their inventory saved or they are teleported. Only joins to play are asked: spectating and an admin forcing a player in go around it, and /events join follows a refused join by letting the player watch instead, a cancelled one included. getEvent() does not count them yet.

GamePlayerEliminateEvent fires before the game checks whether that leaves a winner, and getEvent() already counts them out. A death the game respawns them from is not an elimination, and neither is finishing a course.

GamePlayerLeaveEvent covers a leave command, a menu, another plugin, the API and disconnecting. Being eliminated is not leaving, and neither is the run ending — that is GameEndEvent's to report.

GameEndEvent fires once per run, before anybody is sent home, so getEvent() still lists everybody. getReason() is a GameEndReason:

ConstantMeaning
FINISHEDPlayed to a result — winners or not — and announced.
STOPPEDStopped before a result: by an admin, a shutdown, a waiting event nobody joined in time, or the last player walking out before it began. Nobody wins one.

getWinners() holds more than one UUID for a team game or a shared result, is empty for a stopped run, and its players need not still be online. When the last player's leave stops a run, that run's GameEndEvent arrives before their GamePlayerLeaveEvent.

Not always the main thread

Joins and leaves fire on the thread that owns the player; a start, a begin and an end fire on the thread that asked for them, or the global thread when the clock or a schedule did. On Folia none of those is a single main thread, so schedule anything that touches the wider world.

Registering your own minigame

A plugin outside the suite can add a minigame that behaves like a built-in one: an admin configures arenas of it in the same menus, and it is validated, scheduled, protected, scored and paid out by the same code.

Needs exylia-api v1.7.0

registerMinigame and the net.exylia.lib.api.events.custom package arrived in exylia-api v1.7.0 (ExyliaLib 1.186.0). An older artifact has none of it.

// plugin.yml: depend: [ ExyliaLib, ExyliaEvents ]
ExyliaAPI.get(EventsService.class).ifPresent(events ->
    events.registerMinigame(this, SkyfallMinigame.DEFINITION));

depend rather than softdepend, so your plugin enables after ExyliaEvents and the service is there to register with. There is nothing to unregister: a plugin disabling takes its minigames with it and any run still going is ended first, while the arenas an admin configured stay in the database and come back with the minigame. Registration is refused, with the reason in the console, when the id is already taken, so prefix it with your plugin's name.

A minigame crosses as data rather than a subclass, because every built-in one extends a class inside ExyliaEvents' own classloader that no other plugin can reach.

You writeExyliaEvents does
MinigameDefinition — id, name, icon, settings, markers, scoreboardthe admin setup flow, the arena menus, the stored defaults, the validation
MinigameHandler — start, tick, eliminate, winthe lobby, the countdown, the teleports, the inventories, the kits, the spectator seat, the arena protection, the statistics, the rewards, the cross-server announcement

Declared settings become the defaults a fresh arena is created with and a generated settings screen under the id the admin menus already open; a server owner who wants to restyle it writes menus/admin/<your-id>_settings.yml and that file wins. Declared markers appear on the arena setup screen, block enabling an arena that is missing a required one, and tell the handler when a player walks in or out. A fresh handler is built for every run, so several arenas of the same minigame can play at once, and every callback has a default.

Teams are not supported yet: a definition describes a free-for-all, and the team selector and per-team spawns the built-in team variants use are not reachable from here.

The full guide, with a worked example, is custom-minigames.md.

Types

TypeWhat it is
GameEventOne running event as it was when you asked.
EventDefinitionOne configured event: id, displayName, type, enabled.
EventStatsOne player's counters: kills, deaths, wins, gamesPlayed.
GameStateWhere a running event is in its life.
GameEndReasonWhy a run ended, carried by GameEndEvent.

GameEvent

A snapshot, not a live view: an event's player set, state and clock change every tick, so the values are the ones that were current at the moment of the lookup. Ask again rather than holding one across ticks.

id is the id of this run and lives only as long as the event does. configId is the id of the configuration it was started from and is the same across every run of it — empty when the event was built without one. A leaderboard, a statistic or a menu entry keys on configId; a join or a force-end names the id.

The rest is type (which minigame, for example tntrun; empty when the configuration is gone), displayName, description, state, minPlayers, maxPlayers, the unmodifiable players and spectators sets, alivePlayers, and remainingSeconds as the event counts it — an event with no time limit never counts it down. players includes eliminated players.

playerCount() is the player count with spectators excluded. isFull() is what greys a button out, not what decides: being full is not the only reason a join is refused, the state has to allow it too.

EventDefinition

What an event is started from, and it exists whether or not anybody is playing. Only the four fields a plugin outside the suite can act on are here — the arena bounds, the spawn points, the reward tables and the per-minigame settings are the plugin's own business and change shape between releases. id is the key every statistic is filed under.

EventStats

The same four numbers answer both questions, so one record serves stats and eventStats; which scope a value belongs to is decided by the call that returned it, not by the record. kdr() reports the kill count for a player who has never died rather than infinity, because a leaderboard has to sort it and a menu has to print it. winRate() is 0 to 100, and 0 for somebody who has played nothing.

GameState

ConstantMeaning
WAITINGOpen, filling up, waiting for enough players to start.
STARTINGFull enough and counting down; still joinable until it reaches zero.
PLAYINGBeing played.
ENDINGOver, showing the winner and paying out before it cleans itself up.
DISABLEDStopped by an admin or by a failure, and about to be removed.

isJoinable() is true while the event is filling or counting down; isActive() only while it is being played.

What it does not expose

No arena editor, no spawn or reward writes, no reading or writing the settings of a minigame you did not declare, no gauntlet or inscription management, and no way to write a statistic. The registry that decides whether a player is already busy elsewhere is internal too — from outside, ask each plugin's own service, which for this one is isInEvent.

If an integration genuinely needs something that is not here, ask on Discord — a method added to a service is a minor release.

Something missing on this page? Tell us on Discord