API
Leer qué está corriendo, qué está configurado y qué hizo un jugador, meter o sacar gente de un evento, y seguir su ciclo de vida.
EventsService es con lo que otro plugin averigua si un jugador está dentro de un minijuego, qué está
corriendo o se puede arrancar ahora mismo, y con lo que mete o saca a alguien. Seis eventos de Bukkit
cuentan la vida de cada partida a medida que pasa.
ExyliaAPI.get(EventsService.class).ifPresent(events ->
events.eventOf(player.getUniqueId())
.ifPresent(event -> player.sendMessage("Playing " + event.displayName())));forceStart, openMenu y los eventos del ciclo de vida llegaron en v1.133.0. Un artefacto anterior
no tiene ninguno.
El artefacto, el repositorio y la línea del plugin.yml son los mismos para todos los plugins de
Exylia y viven en la página de la API pública.
Definiciones y ejecuciones
Una EventDefinition es lo que armó un admin; un GameEvent es una partida de eso. Las definiciones
sobreviven a las partidas y son bajo lo que se archivan las estadísticas, así que todo lo que persiste
— una tabla, un menú, un placeholder — se apoya en el id de una definición, mientras que entrar,
espectar o forzar el final nombran el id de una partida que existe ahora mismo.
Todo lo que devuelve un valor lee de las cachés del plugin y es seguro desde el redibujado de un menú o desde un placeholder. Todo lo que actúa mueve jugadores entre mundos, guarda y vacía inventarios y escribe filas, así que llamalo desde el hilo principal y no más seguido de lo que un jugador podría dispararlo.
Eventos en curso
| Método | Qué hace |
|---|---|
Optional<GameEvent> eventOf(UUID) | El evento en el que está un jugador, o vacío si no está en ninguno. |
Optional<GameEvent> eventById(String eventId) | Un evento en curso por el id de la partida, o vacío si no hay ninguna con ese id. |
List<GameEvent> runningEvents() | Todos los eventos que corren ahora mismo, sin orden particular. |
List<GameEvent> joinableEvents() | Todos los eventos a los que todavía se podría dejar entrar a un jugador. |
boolean isInEvent(UUID) | Si algún evento lo tiene, jugando o espectando. |
boolean isPlaying(UUID) | Si es un participante que todavía no fue eliminado. |
boolean isSpectating(UUID) | Si está mirando un evento en curso. |
eventOf e isInEvent cubren tanto espectar como jugar: para todo lo demás en el servidor, un jugador
que mira un evento está en el evento, que es lo que en realidad pregunta un plugin de chat, de
scoreboard o de teletransporte. isSpectating cubre también al eliminado — un jugador eliminado se
queda en el evento mirando el resto, así que desde afuera son lo mismo.
joinableEvents() filtra solo por estado. Un evento de esa lista puede estar lleno igual, así que un
menú que los ofrezca debería mirar GameEvent.isFull() antes de prometer nada.
Definiciones
| Método | Qué hace |
|---|---|
Optional<EventDefinition> definition(String configId) | Un evento configurado, o vacío si no hay nada configurado con ese id. |
List<EventDefinition> definitions() | Todos los eventos configurados, deshabilitados incluidos. |
List<EventDefinition> startableDefinitions() | Todos los que se podrían arrancar ahora: habilitados, completamente armados y no corriendo ya. |
startableDefinitions() es la lista que debería ofrecer un menú de "arrancar un evento", porque las
otras dos ofrecerían entradas que start(String) va a rechazar.
Estadísticas
| Método | Qué hace |
|---|---|
Optional<EventStats> stats(UUID) | Los contadores de un jugador en todos los eventos, o vacío mientras la primera lectura sigue en vuelo. |
Optional<EventStats> eventStats(UUID, String configId) | Los mismos contadores dentro de un evento configurado, vacío por la misma razón. |
Las dos leen de la caché. Un jugador por el que todavía nadie preguntó responde vacío y dispara la lectura, así que un placeholder dibujado en el hilo principal nunca espera a la base de datos y el dibujado siguiente ya tiene el número.
Acciones
Cada una ejecuta el mismo flujo que el comando del propio jugador, con los chequeos de permisos, la
reserva del jugador y los mensajes que ve. Un false es una negativa que ya se le explicó.
| Método | Qué hace |
|---|---|
boolean join(Player, String eventId) | Mete a un jugador en un evento en curso. true si después quedó adentro. |
boolean leave(Player) | Saca al jugador del evento que lo tenga, jugando o espectando. |
boolean spectate(Player, String eventId) | Mete a un jugador en un evento en curso como espectador. |
Optional<GameEvent> start(String configId) | Arranca una partida nueva de un evento configurado, o vacío si fue rechazada. |
boolean forceEnd(String eventId) | Termina un evento en curso ahora mismo, sin ganador. |
boolean forceStart(String eventId) | Empieza el juego de una partida ya, sin esperar la cuenta atrás. true si arrancó. |
start se rechaza cuando la definición está deshabilitada, incompleta o ya corriendo: una configuración
aloja una partida por vez, porque la arena es parte de la configuración.
forceEnd es el corte administrativo: a los jugadores se les devuelve el inventario y se los manda a
casa, y no se paga nada. Usalo para una decisión de moderación o un apagado, no para terminar una
partida.
forceStart es el arranque administrativo, para un anfitrión que no quiere esperar: no mira el mínimo
de jugadores, solo que haya alguien en la partida. Se rechaza si la partida ya se está jugando o
terminando, o si todavía no hay nadie adentro.
Menús
| Método | Qué hace |
|---|---|
void openMenu(Player) | Abre a un jugador el menú de eventos, el mismo que abre /events. |
La pantalla que quiere un NPC de lobby o un objeto de la hotbar: los eventos en curso, la forma de entrar y el historial del propio jugador, dibujada con los archivos de menú del servidor. Llamalo desde el hilo dueño del jugador, como ya lo es un handler de interacción.
Eventos del ciclo de vida
Seis eventos de Bukkit en net.exylia.lib.api.events.event siguen una partida desde que arranca hasta
que termina. Cada uno lleva una foto GameEvent o una EventDefinition, nunca el juego vivo. Dos se
pueden cancelar, y los dos se disparan antes de quitarle nada a nadie.
| Evento | Cuándo | Cancelable |
|---|---|---|
GameStartEvent | Una partida de una configuración está por abrirse. getDefinition(), getStarter(). | Sí |
GamePlayerJoinEvent | Un jugador está por entrar a una partida para jugarla. getPlayer(), getEvent(). | Sí |
GameBeginEvent | Terminó la cuenta atrás: los jugadores están en la arena y corre el reloj. getEvent(). | No |
GamePlayerEliminateEvent | Un jugador quedó fuera del juego y se queda en el evento mirando. getPlayer(), getEvent(). | No |
GamePlayerLeaveEvent | Un jugador salió de la partida, jugando o mirando. getPlayer(), getEvent(). | No |
GameEndEvent | La partida terminó. 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 se dispara cuando pasaron todos los chequeos — habilitada, armada, de este
servidor, sin estar ya corriendo, dentro de los límites — y antes de que exista la partida. Todo
arranque pasa por acá: un comando, el menú de admin, el horario, el programador aleatorio, un pedido de
otro servidor y start(String) por igual, así que es el único lugar para frenar eventos. Un arranque
cancelado no deja partida, ni anuncio, ni hueco ocupado en los límites, ni cooldown. getStarter()
viene vacío para un horario o una llamada de API. El plugin no puede saber por qué lo rechazaste, así
que avisale vos a quien lo lanzó.
GamePlayerJoinEvent se dispara cuando la partida tiene lugar y el jugador está libre, en un mundo
permitido e inscripto donde el evento lo pide, antes de reservarlo, guardar su inventario o
teletransportarlo. Solo se pregunta por entradas para jugar: espectar y que un admin fuerce a un
jugador adentro no pasan por acá, y /events join sigue a una entrada rechazada dejando que el jugador
mire, cancelada incluida. getEvent() todavía no lo cuenta.
GamePlayerEliminateEvent se dispara antes de que el juego mire si eso deja un ganador, y
getEvent() ya lo cuenta fuera. Una muerte de la que el juego lo hace reaparecer no es una
eliminación, y terminar un recorrido tampoco.
GamePlayerLeaveEvent cubre un comando de salida, un menú, otro plugin, la API y desconectarse.
Quedar eliminado no es salir, y que termine la partida tampoco — eso lo informa GameEndEvent.
GameEndEvent se dispara una vez por partida, antes de mandar a nadie a casa, así que getEvent()
todavía los lista a todos. getReason() es un GameEndReason:
| Constante | Significado |
|---|---|
FINISHED | Jugada hasta un resultado — con ganadores o sin ellos — y anunciada. |
STOPPED | Detenida antes de un resultado: por un admin, un apagado, un evento en espera al que nadie entró a tiempo, o el último jugador saliendo antes de empezar. Nadie la gana. |
getWinners() tiene más de un UUID en un juego por equipos o un resultado compartido, viene vacío en
una partida detenida, y sus jugadores no tienen por qué seguir conectados. Cuando la salida del último
jugador detiene una partida, su GameEndEvent llega antes que su GamePlayerLeaveEvent.
Las entradas y salidas se disparan en el hilo dueño del jugador; un arranque, un comienzo y un final, en el hilo que los pidió, o en el hilo global cuando fue el reloj o un horario. En Folia ninguno de esos es un único hilo principal, así que programá todo lo que toque el resto del mundo.
Registrar tu propio minijuego
Un plugin de afuera de la suite puede agregar un minijuego que se comporta como uno de los que vienen incluidos: un admin configura arenas de él en los mismos menús, y lo valida, agenda, protege, puntúa y paga el mismo código.
registerMinigame y el paquete net.exylia.lib.api.events.custom llegaron en exylia-api v1.7.0
(ExyliaLib 1.186.0). Un artefacto anterior no tiene nada de eso.
// plugin.yml: depend: [ ExyliaLib, ExyliaEvents ]
ExyliaAPI.get(EventsService.class).ifPresent(events ->
events.registerMinigame(this, SkyfallMinigame.DEFINITION));depend y no softdepend, así tu plugin enciende después de ExyliaEvents y el service está para
registrarse. No hay nada que desregistrar: un plugin que se apaga se lleva sus minijuegos, y una
partida en curso se termina primero, mientras que las arenas que configuró un admin quedan en la base
de datos y vuelven con el minijuego. El registro se rechaza, con el motivo en la consola, cuando el id
ya está tomado, así que ponle de prefijo el nombre de tu plugin.
Un minijuego cruza como datos y no como subclase, porque cada uno de los incluidos extiende una clase del classloader propio de ExyliaEvents que ningún otro plugin alcanza.
| Vos escribís | ExyliaEvents hace |
|---|---|
MinigameDefinition — id, nombre, ícono, ajustes, marcadores, scoreboard | el flujo de setup del admin, los menús de arena, los valores por defecto guardados, la validación |
MinigameHandler — start, tick, eliminación, victoria | el lobby, la cuenta atrás, los teleports, los inventarios, los kits, el asiento de espectador, la protección de la arena, las estadísticas, las recompensas, el aviso entre servidores |
Los ajustes declarados pasan a ser los valores con los que se crea una arena nueva y una pantalla de
ajustes generada bajo el id que los menús de admin ya abren; un dueño de servidor que quiera
reestilarla escribe menus/admin/<tu-id>_settings.yml y ese archivo gana. Los marcadores declarados
aparecen en la pantalla de setup de la arena, impiden habilitar una arena a la que le falta uno
obligatorio, y le avisan al handler cuando un jugador entra o sale. Se construye un handler nuevo por
cada partida, así que varias arenas del mismo minijuego pueden jugarse a la vez, y cada callback tiene
un valor por defecto.
Los equipos todavía no están soportados: una definición describe un todos contra todos, y el selector de equipos y los spawns por equipo que usan las variantes incluidas no se alcanzan desde acá.
La guía completa, con un ejemplo trabajado, es custom-minigames.md.
Tipos
| Tipo | Qué es |
|---|---|
GameEvent | Un evento en curso como estaba cuando preguntaste. |
EventDefinition | Un evento configurado: id, displayName, type, enabled. |
EventStats | Los contadores de un jugador: kills, deaths, wins, gamesPlayed. |
GameState | En qué punto de su vida está un evento en curso. |
GameEndReason | Por qué terminó una partida, lo lleva GameEndEvent. |
GameEvent
Una foto, no una vista viva: el conjunto de jugadores, el estado y el reloj de un evento cambian cada tick, así que los valores son los que estaban vigentes en el momento de la consulta. Volvé a preguntar en vez de guardarla entre ticks.
id es el id de esta partida y vive solo mientras dura el evento. configId es el id de la
configuración desde la que se arrancó y es el mismo en todas sus partidas — vacío si el evento se armó
sin una. Una tabla, una estadística o una entrada de menú se apoyan en configId; entrar o forzar el
final nombran el id.
El resto es type (qué minijuego, por ejemplo tntrun; vacío si la configuración ya no está),
displayName, description, state, minPlayers, maxPlayers, los conjuntos inmutables players y
spectators, alivePlayers y remainingSeconds tal como los cuenta el evento — uno sin límite de
tiempo nunca los descuenta. players incluye a los eliminados.
playerCount() es la cantidad de jugadores sin contar espectadores. isFull() es lo que apaga un
botón, no lo que decide: estar lleno no es la única razón por la que se rechaza una entrada, el estado
también tiene que permitirla.
EventDefinition
Es aquello desde lo que se arranca un evento, y existe juegue alguien o no. Acá están solo los cuatro
campos sobre los que puede actuar un plugin de afuera de la suite — los límites de la arena, los puntos
de aparición, las tablas de recompensas y los ajustes por minijuego son asunto del plugin y cambian de
forma entre releases. id es la clave bajo la que se archiva cada estadística.
EventStats
Los mismos cuatro números responden las dos preguntas, así que un solo record sirve para stats y para
eventStats; a qué alcance pertenece un valor lo decide la llamada que lo devolvió, no el record.
kdr() devuelve la cantidad de kills para un jugador que nunca murió en vez de infinito, porque una
tabla lo tiene que ordenar y un menú lo tiene que imprimir. winRate() va de 0 a 100, y es 0 para
alguien que no jugó nada.
GameState
| Constante | Significado |
|---|---|
WAITING | Abierto, llenándose, esperando jugadores suficientes para arrancar. |
STARTING | Con gente suficiente y en cuenta atrás; todavía se puede entrar hasta que llegue a cero. |
PLAYING | En juego. |
ENDING | Terminado, mostrando al ganador y pagando antes de limpiarse solo. |
DISABLED | Detenido por un admin o por una falla, y a punto de eliminarse. |
isJoinable() es true mientras el evento se llena o cuenta atrás; isActive() solo mientras se está
jugando.
Lo que no expone
Nada de editor de arenas, escrituras de spawns o recompensas, leer o escribir los ajustes de un
minijuego que no declaraste, manejo de gauntlets o inscripciones, ni forma de escribir una
estadística. El registro que decide si un jugador ya está ocupado en otro lado también es interno —
desde afuera se le pregunta al service de cada plugin, que para este es isInEvent.
Si una integración realmente necesita algo que no está acá, pídelo en Discord — agregar un método a un service es un release menor.
¿Falta algo en esta página? Dínoslo en Discord