API para desarrolladores
Dungeon expone una pequeña API para que otras extensiones de Typewriter puedan leer las partidas (runs) del servidor, actuar sobre ellas y reaccionar a lo que ocurre en ellas. Esta página es para desarrolladores; para construir mazmorras no la necesitas.
Quién puede usarla
Typewriter carga todas las extensiones en un class loader propio, recreado en cada /tw reload. Por eso las clases de la API son visibles para otras extensiones de Typewriter que declaren dependency("btcrenaud", "Dungeon"), y no para un plugin de Bukkit corriente.
Un plugin que necesite datos de mazmorra debe usar los placeholders y las entradas de Dungeon, o pasar por una pequeña extensión de Typewriter que sirva de puente.
No conserves un DungeonRunView ni un listener de eventos a través de una recarga de la extensión Dungeon. Lee una vista nueva y vuelve a registrar tus listeners cuando se cargue tu extensión.
Solo dos paquetes son públicos y estables:
btcrenaud.dungeons.apibtcrenaud.dungeons.api.event
Todo lo demás es interno y puede cambiar en cualquier versión sin previo aviso.
Puesta en marcha
En el build.gradle.kts de tu extensión, añade Dungeon al classpath de compilación y decláralo como dependencia de Typewriter, como hace la propia Dungeon con otra extensión:
dependencies {
compileOnly(files("libs/TypeWriter-DungeonExtension.jar"))
}
typewriter {
namespace = "yournamespace"
extension {
name = "YourExtension"
// ...
dependencies {
dependency("btcrenaud", "Dungeon")
}
}
}
DungeonApi
DungeonApi es un object de Kotlin. Todas sus funciones pueden llamarse desde cualquier hilo.
Lecturas
| Función | Devuelve |
|---|---|
runOf(player: UUID) | El DungeonRunView de la partida en la que está el jugador, sea cual sea su estado (una partida que termina sigue listando a sus miembros hasta que se libera), o null. |
run(runId: String) | La partida runId, o null si ya no existe. Un id de partida es único y nunca se reutiliza. |
runs() | Todas las partidas del servidor en este momento, sin un orden concreto. Cada vista tiene su propio instante; la lista no es atómica entre partidas. |
runsOf(dungeonId: String) | Las partidas de la mazmorra cuyo id de entrada dungeon_instance es dungeonId. Vacío para una mazmorra desconocida. |
isInRun(player: UUID) | Si el jugador está en una partida en preparación o activa: la misma pregunta que se hace la mazmorra antes de dejar que alguien inicie o se una a una partida. false para un jugador cuya partida ya está terminando. |
roomsOf(player: UUID) | Los ids de las salas en las que está el jugador, tal como la mazmorra las vio por última vez: los ids de entrada dungeon_room de una mazmorra fija, generated:<n> en una generada. Vacío si no está en ninguna partida o está en una partida sin salas. |
aliveMobs(runId: String, mobEntryId: String? = null) | Cuántos mobs de la mazmorra de la partida están vivos ahora, solo los creados por la entrada dungeon_mob_spawn mobEntryId si se indica. 0 para una partida desconocida. |
El límite de mobs vivos puede retener algunos, y justo después de una recarga los mobs se vuelven a contar chunk a chunk. Para saber que una zona se despejó, escucha DungeonMobsClearedEvent.
Acciones
Cada acción devuelve un DungeonActionResult:
| Resultado | Significado |
|---|---|
Done | La acción fue aceptada. Algunas acciones terminan en un tick posterior; el evento correspondiente dice cuándo. |
NotFound(what) | Lo que nombra la llamada no existe (ya); what indica qué cosa falta. |
Refused(reason) | La llamada nombra cosas reales pero la mazmorra se niega a hacerlo ahora. No se cambió nada. |
Los textos de what y reason están en inglés y son para el desarrollador que lee un registro; nunca se muestran a los jugadores.
| Acción | Qué hace | Refused cuando |
|---|---|---|
endRun(runId) | Termina la partida como lo haría un administrador (dungeon_close): los jugadores son enviados a casa y el mundo se libera. La secuencia de fin se ejecuta en un tick posterior; DungeonRunEndEvent (motivo ADMIN) indica que empezó. | La partida aún se está preparando (un lanzamiento no se cancela desde aquí: escucha DungeonRunPreLaunchEvent), o ya otra llamada posee su fin: una partida termina una sola vez. NotFound("run") para una partida desconocida. |
leaveRun(player) | Saca al jugador de su partida, como la acción dungeon_leave: se le devuelve a donde estaba. Si era el último, la partida termina justo después. DungeonPlayerLeaveRunEvent indica que se fue. | La partida aún se está preparando (el lanzamiento posee a sus miembros hasta que está activa), o ya está terminando (su secuencia de fin envía a los miembros a casa). NotFound si el jugador no está en ninguna partida. |
placeChest(runId, chestEntryId) | Coloca el cofre de botín de la entrada dungeon_loot_chest en la partida, como un Loot Chest Trigger pero sin su sonido, partículas ni mensaje. Colocar un cofre ya colocado no cambia nada. | La entrada no está en modo manual, pertenece a otra mazmorra distinta de la de la partida, la partida no está activa o el mundo de la partida no está cargado. NotFound para una partida desconocida ("run") o una entrada desconocida ("loot chest entry"). |
Hilos
- Las lecturas son seguras desde cualquier hilo: leen una instantánea inmutable del registro, y
aliveMobscuenta sin tomar ningún bloqueo. - Las acciones también son seguras desde cualquier hilo y nunca esperan al juego: lo que debe ejecutarse en el hilo del servidor se entrega a los planificadores del propio servidor.
leaveRunllamado desde el hilo del servidor: el jugador se ha ido cuando devuelveDone. Llamado desde otro hilo: la salida se ejecuta en el siguiente tick del hilo del servidor yDonesignifica aceptado; el evento indica que ocurrió.placeChest:Donesignifica aceptado. El cofre aparece en el siguiente tick, en el hilo que posee su bloque;DungeonChestPlaceEventindica que ya está puesto. Puede que no (un bloque que no se puede reemplazar), lo cual se registra en el log.
DungeonRunView
Una partida congelada en un instante. Es una copia que nunca cambia tras crearse: se puede conservar, comparar y leer desde cualquier hilo; pide a DungeonApi una nueva cuando la necesites. Todos los campos proceden de una sola lectura del registro, así que state y members siempre concuerdan.
| Campo | Tipo | Significado |
|---|---|---|
runId | String | Id único de la partida; desaparece con ella. |
dungeonId | String | Id de la entrada dungeon_instance que juega la partida. |
dungeonName | String | Nombre visible de esa entrada, como lo escribió el autor (puede estar vacío). |
state | DungeonRunState | En qué punto de su vida está la partida. |
members | List<UUID> | Los jugadores de la partida en orden de llegada: el primero es el líder. Conectados o no (un sitio reservado cuenta). |
worldName | String? | El mundo donde se juega la partida; null hasta que está listo. |
startedAtEpochMs | Long | Cuándo se reservó el sitio de la partida, en milisegundos epoch. |
activatedAtEpochMs | Long? | Cuándo se dejó entrar a los jugadores; null durante la preparación y en una partida recuperada tras una recarga. |
deadlineEpochMs | Long? | Cuándo debe terminar la partida; null en una partida sin límite de tiempo (o aún no activa). |
seed | Long? | La semilla que creó la mazmorra, solo en una mazmorra generada; null en una fija. Reutilizarla da la misma mazmorra. |
generated | Boolean | Propiedad calculada: true cuando la mazmorra la hizo un generador (tiene seed). |
DungeonRunState
| Valor | Significado |
|---|---|
PREPARING | El sitio está reservado y el mundo se está preparando; los jugadores aún no están dentro. |
ACTIVE | Los jugadores están dentro y se está jugando la mazmorra. |
ENDING | La partida ha terminado o se está cerrando: los miembros son enviados a casa y el mundo liberado. Cubre también el instante entre el fin de esa secuencia y la desaparición de la partida, y un lanzamiento que falló tras su reserva. |
Este enum pertenece a la API y no sigue la máquina de estados interna: la plataforma puede añadir un estado interno sin romper tu when.
DungeonEndReason
| Valor | Significado |
|---|---|
COMPLETED | La mazmorra se completó (dungeon_complete). |
TIMEOUT | Se agotó el límite de tiempo de la mazmorra. |
ABANDONED | Todos los jugadores se fueron. |
ADMIN | Un administrador la cerró (dungeon_close, el comando de cierre, DungeonApi.endRun). |
DungeonDeparture
| Valor | Significado |
|---|---|
SENT_HOME | Se fue a propósito (la acción o el comando de salir, DungeonApi.leaveRun) y se le teletransporta a donde estaba. |
DISCONNECTED | Se desconectó (o se agotó su plazo de reconexión): se le devolverá a donde estaba en su próxima conexión. |
LEFT_THE_WORLD | Salió del mundo de la partida por sus propios medios, así que no hay desde dónde devolverlo. |
Eventos
Todos los eventos son eventos de Bukkit del paquete btcrenaud.dungeons.api.event. Escucha el evento concreto que necesites.
| Evento | Cancelable | Cuándo | Campos |
|---|---|---|---|
DungeonRunPreLaunchEvent | Sí | Un grupo está a punto de iniciar una mazmorra: el lanzamiento superó sus propias comprobaciones (tamaño del grupo, criterios, coste de entrada...) y aún no se ha reservado, cobrado ni creado nada. | dungeonId: String, members: List<UUID> (solo jugadores conectados, en el orden dado), forced: Boolean (un administrador forzó el inicio), seed: Long? (la semilla pedida; null cuando el lanzamiento sortea la suya, o en una mazmorra fija) |
DungeonRunStartEvent | No | Una partida empezó: está ACTIVE y sus jugadores están dentro, tras ejecutarse los disparadores onStart y los eventos on_dungeon_start. | run |
DungeonRunEndEvent | No | Una partida está terminando: en cuanto empieza la secuencia de fin y se pagan las estadísticas, antes de los disparadores onEnd, la limpieza de cofres y mobs y el regreso de los jugadores. | run (estado ENDING, miembros que siguen dentro en ese instante), reason: DungeonEndReason |
DungeonPlayerLeaveRunEvent | No | Un jugador salió de una partida, de la forma que sea. Justo después de retirarlo de la partida. | run (ya no lista al jugador), player: UUID (puede estar desconectado), departure: DungeonDeparture |
DungeonRoomEnterEvent | No | Un miembro entró en una sala, incluso si el enterOnce de la sala ya gastó sus disparadores. | run, player: Player, roomId: String, templateId: String? |
DungeonRoomLeaveEvent | No | Un miembro salió de una sala, incluso si leaveOnce ya gastó sus disparadores. También sigue a la salida de un jugador de la partida (o de su mundo) mientras estaba en una sala. | run, player: Player, roomId: String, templateId: String? |
DungeonMobDeathEvent | No | Un mob de la mazmorra murió mientras su partida estaba ACTIVE. Los trozos de un mob que se divide (slimes) y otros descendientes no lanzan nada. | run, entity: LivingEntity, killer: Player? (null si lo mató otra cosa), mobEntryId: String |
DungeonMobsClearedEvent | No | Una zona se despejó: murió su último mob de su última oleada. Una vez por despeje, no una vez por miembro. | run, mobEntryId: String |
DungeonChestPlaceEvent | No | Se colocó un cofre de botín, una vez por cofre, al registrarse por primera vez. Un cofre recuperado tras un /tw reload no lanza nada. | run, chestId: String, chestEntryId: String, location: Location (copia de la posición del bloque) |
DungeonChestOpenEvent | Sí | Un miembro está a punto de abrir (o activar) un cofre de botín, haciendo clic en su bloque o en su NPC. | run, player: Player, chestId: String, chestEntryId: String, location: Location |
Notas sobre los campos:
roomIdes el id de la entradadungeon_roomen una mazmorra fija, ygenerated:<n>en una sala de una mazmorra generada, cuyotemplateIdes el id de la plantilla de sala de la que se creó (nullen una mazmorra fija).chestIdnombra este cofre concreto en la partida: el id de la entrada para su lugar principal,<entry>#<n>para una ubicación adicional,<entry>@<room>.<marker>para el cofre de un marcador.chestEntryIdes en todos los casos el id de la entradadungeon_loot_chest.DungeonRunPreLaunchEventno tieneDungeonRunView: la partida aún no existe. Un lanzamiento que pasó este evento aún puede fallar después; soloDungeonRunStartEventdice que una partida empezó de verdad. Un lanzamiento que falló antes de que la partida pasara aACTIVEno lanza ningún evento de fin.
Cancelar
DungeonRunPreLaunchEvent: un lanzamiento cancelado se rechaza sin dejar rastro (sin sitio reservado, sin coste cobrado, sin mundo creado). Por defecto a los jugadores no se les dice nada, porque el snippetcontent.dungeon_launch.cancelledestá vacío: la extensión que cancela debe explicárselo ella misma a los jugadores, o el dueño del servidor rellena ese snippet. Un inicio desde un lobby devuelve al grupo al lobby.DungeonChestOpenEvent: un clic cancelado se consume. El cofre no se abre, no se sortea nada, no se ejecuta ninguna acción, ninguna estadística cuenta, y el cofre queda como estaba para que el jugador pueda reintentarlo. Explícale tú mismo el motivo al jugador; por defecto no se dice nada.
Contrato común a todos los eventos
- Un evento se lanza una vez por hecho, nunca una vez por miembro: un
DungeonRunEndEventpor partida, tenga los jugadores que tenga. - Se lanza en el hilo que produjo el hecho: el hilo del servidor en Paper.
- Un listener que lanza una excepción es registrado por el servidor e ignorado; nunca puede romper una partida. Un listener tampoco debe bloquear, ya que se ejecuta en línea en medio de la secuencia propia de la partida.
- Escuchar la clase base
DungeonEventno está soportado: Bukkit se niega a registrarla. Escucha los eventos concretos. - Los eventos describen la carga actual de la extensión. Un
/tw reloadno lanza ningún evento de fin ni de salida para las partidas que conserva: se readoptan en silencio y sus hechos posteriores se lanzan con normalidad. - Cuando un jugador sale de una partida estando en una sala,
DungeonRoomLeaveEventllega antes que elDungeonPlayerLeaveRunEventde la misma salida, y surun.membersya no lista al jugador.
Ejemplos
Un listener que cancela un lanzamiento bajo una condición y avisa a los jugadores:
import btcrenaud.dungeons.api.event.DungeonRunPreLaunchEvent
import net.kyori.adventure.text.Component
import org.bukkit.Bukkit
import org.bukkit.event.EventHandler
import org.bukkit.event.Listener
class LaunchGate(private val isMaintenance: () -> Boolean) : Listener {
@EventHandler
fun onPreLaunch(event: DungeonRunPreLaunchEvent) {
if (event.forced || !isMaintenance()) return
event.isCancelled = true
// Nadie más lo explicará: el snippet por defecto está vacío.
event.members
.mapNotNull { Bukkit.getPlayer(it) }
.forEach { it.sendMessage(Component.text("Las mazmorras están cerradas por mantenimiento.")) }
}
}
Leer la partida de un jugador:
import btcrenaud.dungeons.api.DungeonApi
import java.util.UUID
fun describeRun(player: UUID): String {
val run = DungeonApi.runOf(player) ?: return "No está en una mazmorra."
return "${run.dungeonName} (${run.state}): ${run.members.size} jugador(es)"
}