Contenido generado con IA — puede contener errores.

Referenciadev

API

Leer arenas, kits, partidas, colas, parties y ranking, y preguntar si un jugador está libre antes de llevártelo.

PracticeService es la forma en que otro plugin lee y maneja ExyliaPracticeCore: qué está haciendo un jugador, qué se está peleando y dónde, y los mismos flujos que ejecutan los comandos del propio jugador.

ExyliaAPI.get(PracticeService.class).ifPresent(practice ->
    practice.matchOf(player.getUniqueId())
            .ifPresent(match -> player.sendMessage("Peleando en " + match.kitId())));
Agregarlo a tu proyecto

El artefacto, el repositorio y la línea del plugin.yml son los mismos para todos los plugins de Exylia y están en la página de la API pública.

Pregunta antes de mover a un jugador

El practice se adueña de un jugador mientras esté en cola, peleando, viendo una partida o editando un kit, y todo eso se rompe si lo teletransportas a otro lado. isAvailable(UUID) es la pregunta que tiene que hacer cualquier otro modo antes de nada; state(UUID) dice por qué la respuesta fue no.

Las consultas son baratas, las acciones no

Todo lo que devuelve un valor lee de la caché del plugin y es seguro desde el redibujado de un menú o desde un placeholder — los dos métodos que devuelven CompletableFuture son la excepción, y los dos se delatan solos. Todo lo demás ejecuta el mismo flujo que el comando del propio jugador, con sus comprobaciones de permiso, sus cooldowns y sus mensajes, así que llámalo en el hilo principal y no más seguido de lo que un jugador podría provocarlo. Una acción devuelve si el plugin la aceptó; al jugador ya se le dijo por qué no.

Arenas

MétodoQué hace
Optional<Arena> arena(String arenaId)Una arena por id. Vacío si ninguna la tiene.
List<Arena> arenas()Todas las arenas, activadas o no, como snapshot de la caché. Vale para un menú, sobra dentro de un bucle.

Kits

MétodoQué hace
Optional<Kit> kit(String kitId)Un kit por id. Vacío si ninguno lo tiene.
List<Kit> kits()Todos los kits, activados o no.
List<Kit> kitsPlayableIn(String arenaId)Los kits jugables en una arena. No es lo mismo que filtrar Kit.compatibleArenas() por tu cuenta: un kit que no nombra ninguna arena se puede jugar en todas. Vacío si la arena no existe.
List<KitCategory> kitCategories()Todas las categorías de kit, activadas o no.
List<KitLoadout> loadouts(UUID player, String kitId)Los loadouts guardados de un jugador para un kit. Se leen de la copia cargada al entrar, así que un jugador desconectado no tiene ninguno.
boolean applyKit(Player player, String kitId)Le da el kit al jugador como haría una partida: limpia lo que llevaba y aplica los efectos y reglas del kit, usando su propio loadout si guardó alguno. false si ningún kit tiene ese id.

Estado del jugador

MétodoQué hace
PracticeState state(UUID player)Qué está haciendo el jugador. AVAILABLE para cualquiera que el plugin no esté reteniendo.
boolean isAvailable(UUID player)Si nadie tiene un claim sobre él. No es lo mismo que comparar state(UUID) con AVAILABLE, porque también puede estar retenido por un plugin que no es este.
boolean isInMatch(UUID player)Si está en una partida. Cierto desde que empieza a montarse, y cierto para un espectador que la mira desde dentro — usa MatchInfo.isActive() cuando necesites que esté peleando de verdad.
boolean isInQueue(UUID player)Si está esperando en al menos una cola.
void sendToLobby(Player player)Lo lleva al lobby del practice y nada más.
boolean leave(Player player)Lo saca de donde esté, rindiendo la partida. Responde si la petición fue aceptada, no si ya terminó; false para un estado sin salida, como una partida todavía cargando o una zona PvP.

Partidas

MétodoQué hace
Optional<MatchInfo> matchOf(UUID player)La partida en la que está, peleando o mirándola desde dentro. Vacío si no está en ninguna.
Optional<MatchInfo> match(String matchId)Una partida por id. Vacío en cuanto termina.
Collection<MatchInfo> activeMatches()Todas las partidas en curso, sin orden concreto.
boolean spectate(Player spectator, String matchId)Manda a un jugador a mirar. Se rechaza para una partida que no empezó o ya terminó, para una contra bots, y para una cuyos peleadores apagaron los espectadores.
boolean spectatePlayer(Player spectator, Player target)Lo mismo, para lo que esté peleando otro jugador. Mirar a alguien que a su vez es espectador se rechaza en vez de seguirlo en silencio.
boolean stopSpectating(Player spectator)Saca al espectador. false si no estaba mirando nada.

Cola

MétodoQué hace
Set<String> queuedKits(UUID player)Los kits en cuya cola espera. Un jugador puede esperar en varias a la vez y se lleva la primera partida que salga.
int queueSize(String kitId)Cuántos jugadores esperan por un kit. 0 para uno en el que no espera nadie.
boolean joinQueue(Player player, String kitId)Lo mete en una cola. Se rechaza si el jugador no está disponible, si sus estadísticas no terminaron de cargar, si la cola está bloqueada por una rotación de temporada, o si el kit no es de cola.
boolean leaveQueue(Player player)Lo saca de todas las colas. Se rechaza una vez encontrada su partida: a esa altura ya no está esperando, y soltarlo lo dejaría libre para meterse en otro modo mientras la partida lo carga.

Duelos

MétodoQué hace
boolean sendDuelRequest(Player sender, Player target, String kitId, int rounds)Manda una petición de duelo. La arena la elige el plugin, entre las que admiten el kit; rounds es 1 para una sola pelea. A los dos jugadores se les avisa qué pasó.
void acceptDuelRequest(Player player, Player sender)Acepta la petición de sender.
void declineDuelRequest(Player player, Player sender)Rechaza la petición de sender.
List<String> pendingDuelSenders(Player target)Quién tiene una petición esperándolo. Nombres, no UUIDs, y solo los que siguen conectados: una petición de alguien que se desconectó no se puede aceptar.

Parties

MétodoQué hace
Optional<Party> partyOf(UUID player)Su party. Vacío si no está en ninguna.
boolean createParty(Player player)Crea una party liderada por el jugador.
boolean inviteToParty(Player leader, Player target)Invita a un jugador a la party del líder.
boolean leaveParty(Player player)Lo saca de su party. Si se va el líder, la party pasa a otro en vez de disolverse.
boolean disbandParty(Player leader)Disuelve la party.

Estadísticas y ranking

MétodoQué hace
Optional<PlayerStats> stats(UUID player, String kitId)Sus números en un kit, esta temporada. Vacío si nunca terminó una partida en él.
Optional<PlayerStats> globalStats(UUID player)Sus números sumando todos los kits, esta temporada. Vacío si nunca terminó una partida.
int elo(UUID player, String kitId)Su ELO en un kit. Devuelve el ELO inicial del servidor para quien nunca lo jugó, porque es con el que lo emparejaría.
Rank rank(UUID player, String kitId)Dónde lo deja su ELO en la escalera visible. El rango más bajo para quien nunca jugó.
CompletableFuture<List<PlayerStats>> leaderboard(String kitId, LeaderboardType type)La cabeza de la tabla de un kit, de mejor a peor. Es una lectura de base de datos, cacheada un rato tras cada una — no la llames por cada fila de un menú. Pasa PlayerStats.GLOBAL_KIT para la tabla global.
CompletableFuture<List<MatchRecord>> matchHistory(UUID player, String kitId)Sus últimas partidas, de la más nueva a la más vieja. Lectura de base de datos, cacheada un rato, y solo llega hasta donde llegue la retención del servidor. Pasa PlayerStats.GLOBAL_KIT para todos los kits.

Llevarte a un jugador del practice

No llames al plugin para liberar a un jugador. Réclamalo por el registro de sesiones de ExyliaLib y el lobby reacciona solo:

Optional<Claim> claim = Sessions.of(myPlugin).claim(player, "MY_MODE");
if (claim.isEmpty()) return;   // alguien se negó a soltarlo

En cuanto entra el claim, el practice le esconde el scoreboard, le quita los ítems del lobby y lo saca del conjunto de visibilidad. Soltar el claim devuelve todo eso — pero solo si nadie más lo tomó mientras tanto, así que pasar a un jugador de un modo a otro no le hace parpadear el kit del lobby en el medio. Pedir a un jugador que el practice está reteniendo rinde su partida, lo saca de su cola o lo saca de su zona PvP primero; un estado sin salida rechaza el claim.

Tipos

Cada record es un snapshot del momento de la consulta. Una partida cambia varias veces por segundo y deja de existir poco después de terminar, así que vuelve a preguntar en vez de guardártela — y busca a los peleadores por UUID en el momento en que actúes sobre ellos.

Arena

id, displayName, enabled, priority, iconMaterial, usages. Los spawns, las regiones y los esquemas con los que se regenera la arena no están aquí: son lo que el plugin necesita para correr una partida, no lo que otro plugin necesita saber.

allows(ArenaUsage usage) comprueba un uso.

ArenaUsage

QUEUE (matchmaking), DUEL (duelos entre jugadores), PARTY (FFA y peleas por equipos de party), BOT (peleas contra bots). Una arena que no declara ninguno los acepta todos, y en ese caso Arena.usages() devuelve el conjunto completo en vez de uno vacío.

Kit

id, displayName, description, enabled, ranked, queueEnabled, duelEnabled, partyEnabled, priority, iconMaterial, categories, compatibleArenas (vacío para cualquiera).

Los cuatro flags de modo van sueltos en vez de en un conjunto porque el servidor los enciende y apaga por separado: un kit puede admitir duelos y no cola mientras un admin lo ajusta. Los ítems que reparte un kit no están aquí — solo significan algo aplicados a un jugador, que es para lo que está applyKit.

KitCategory

id, displayName, description, enabled, priority, iconMaterial. Solo presentación: una categoría decide dónde se dibuja un kit, nunca dónde se puede jugar. Un kit puede estar en varias, o en ninguna.

KitLoadout

kitId, slotIndex, name, iconMaterial. Solo la etiqueta: los ítems son una disposición privada de inventario, y el plugin ya se los da a su dueño cuando empieza la partida.

PracticeState

Solo AVAILABLE significa que el jugador está libre. Cualquier otro valor es algo que se rompería si lo mueves.

ValorSignificado
AVAILABLEParado en el lobby, en nada.
IN_PARTYEn una party que todavía no entró en cola para nada.
IN_QUEUEEsperando partida.
LOADING_MATCHSe le está montando una partida alrededor.
IN_GAMEPeleando.
SPECTATINGMirando una partida.
EDITING_KITEditando un loadout de kit.
EXTERNALRetenido por otro plugin, que lo dijo reclamándolo.
IN_PVP_ZONEDentro de una zona PvP abierta del mundo del lobby.

MatchInfo

id, kitId, arenaId, mode, status, ranked, currentRound, totalRounds, players, alivePlayers, spectators, startedAt (milisegundos epoch, 0 mientras la partida carga) y durationMillis.

isActive() es lo que debe comprobar cualquier cosa que actúe sobre peleadores vivos, en vez de que la partida exista y ya: existe mientras se prepara la arena y un momento después del último golpe. Los modos contra bots reportan un jugador y un rival que no existe, porque un bot no es un jugador y este plugin no lo modela.

MatchMode

DUEL_1V1, PARTY_FFA, PARTY_SPLIT, PARTY_DUEL, BOT_DUEL, BOT_PARTY.

againstBots() es cierto para los dos últimos. Esos no registran nada — ni estadísticas, ni movimiento de ELO, ni fila de historial —, así que una integración que cuente partidas debería saltárselos en vez de preguntarse por qué los números no se mueven.

MatchStatus

LOADING, STARTING, ACTIVE, ROUND_ENDING, POINT_RESETTING, ENDING, ENDED. Una partida existe antes de que se pueda golpear a nadie y un momento después del último golpe, así que comprueba ACTIVE y no que la partida esté presente.

MatchRecord

El lado de un jugador en una partida terminada: matchId, playerUuid, opponentName, kitId, arenaId, result, ranked, eloBefore, eloAfter, eloChange, playedAt, durationMillis, kills, deaths, damageDealt, bestCombo.

Una partida produce uno de estos por jugador, así que la misma pelea es un WIN aquí y un LOSS en la copia del rival — únelas por matchId para ver las dos mitades. Las filas se podan pasada la retención del servidor; todo lo que se deriva de la partida ya se guardó en PlayerStats al terminar.

MatchResult

WIN, LOSS, DRAW. Se guarda por jugador, no por partida.

Party

id, leader, members (con el líder incluido), open, maxSize, más size() e isMember(UUID).

Una party no retiene a nadie por sí sola — sus miembros siguen libres hasta que entra en cola, que es por lo que state(UUID) devuelve IN_PARTY en vez de negarse a que otro modo se lleve a uno.

PlayerStats

Los números de un jugador en un kit y una temporada: playerUuid, playerName, kitId, season, kills, deaths, wins, losses, draws, currentStreak, bestStreak, elo, peakElo, rankedGamesPlayed, damageDealt, damageTaken, bestCombo, timePlayed (milisegundos), winRate (de 0 a 1) y kdr.

Las temporadas reinician: son los números desde que empezó la actual. La fila aparece la primera vez que el jugador termina una partida en el kit, así que quien no tiene fila sencillamente no lo jugó. El global es el mismo record guardado bajo el id de kit PlayerStats.GLOBAL_KIT — el literal global —, que es lo que permite que una sola consulta responda las dos cosas.

Rank

id, displayName, divisionName, divisionNumber, icon, lp, lpMax.

Derivado, no guardado: el archivo de rangos del servidor corta el rango de ELO en bandas con nombre y cada banda en divisiones, así que un rango es una forma de dibujar PlayerStats.elo() y no algo que el jugador posea. Los nombres y el icono vienen escritos en MiniMessage — un rango suele ser un degradado —, así que pásalos por un formateador en vez de imprimirlos literales. No todos los rangos tienen divisiones: cuando no las tienen, divisionName viene vacío y divisionNumber y lpMax son 0, y ahí conviene mostrar el ELO crudo, porque lp no tiene sobre qué contarse.

LeaderboardType

Por qué se ordena una tabla, una columna indexada cada uno: RANK (ELO), KILLS, DEATHS, KDR, WINS, LOSSES, WIN_RATE, BEST_STREAK, CURRENT_STREAK, DAMAGE_DEALT, TIME_PLAYED.

Acciones desde los menús de otro plugin

El plugin registra sus acciones en el registro compartido de ExyliaLib bajo el namespace practice:, así que cualquier menú de ExyliaLib del servidor puede llamarlas:

actions:
  - "practice:join_queue %kit_id%"
  - "left: practice:open_stats"

Las de jugador son el conjunto útil fuera de este plugin: open_queue_categories, join_queue, leave_queue, open_stats, open_history, open_settings, open_kit_editor, open_spectate_games, open_party_hub, leave_game y duel_send_request. Toda acción que cambie una arena, un kit o los datos de alguien comprueba exyliapractice.admin en el propio registro, así que nombrarla en un archivo de jugador no regala nada.

Bots

Los bots son un plugin aparte, ExyliaPracticeBotV3, con su propio service en el mismo artefacto: net.exylia.lib.api.practicebot.PracticeBotService, al que se llega con PracticeBots.get(). Genera un bot a partir de un BotSpec — dueño, punto de aparición, modo de combate, dificultad, si reaparece, y opcionalmente un kit y una skin —, devuelve un BotHandle, encuentra el bot detrás de una entidad o de un jugador, y dice cuántos existen frente al tope configurado. Lanza un evento, PracticeBotDeathEvent, para que nadie tenga que mirar muertes de entidades y adivinar qué cadáveres eran bots; no es cancelable, porque cuando corre el bot ya no está, y se llama en el hilo en el que corría el bot.

Un servidor sin el plugin de bots simplemente responde vacío, que es justamente el punto de una integración blanda.

Lo que no expone

ExyliaPracticeCore no publica eventos propios. Lo que escucha otro plugin en su lugar es el registro de sesiones de ExyliaLib, que informa de todos los modos del servidor y no solo de este:

Sessions.watch(myPlugin,
    claim -> { /* alguien fue tomado */ },
    claim -> { /* alguien fue soltado */ });

El service está curado, no es un espejo del plugin. Los abridores de menú, el editor de kits, el manejo y la regeneración de instancias de arena, la rotación de temporada, las escrituras administrativas sobre las estadísticas de alguien y las filas crudas de configuración quedan afuera a propósito: son el plugin corriéndose a sí mismo, y un método público que metiera mano ahí sería una forma de romper una partida en silencio.

La integración HTTP aparte que publica resultados y rangos en practice-api.exylia.net no es parte de esto — ver Web API.

Si de verdad te falta algo, pídelo en Discord.

¿Falta algo en esta página? Dínoslo en Discord