Entwickler-API
Dungeon stellt eine kleine API bereit, damit andere Typewriter-Erweiterungen die Durchläufe (Runs) des Servers lesen, darauf zugreifen und auf deren Geschehen reagieren können. Diese Seite richtet sich an Entwickler; zum Bauen von Dungeons brauchen Sie sie nicht.
Wer sie nutzen kann
Typewriter lädt alle Erweiterungen in einen eigenen Class Loader, der bei jedem /tw reload neu aufgebaut wird. Die Klassen der API sind daher für andere Typewriter-Erweiterungen sichtbar, die dependency("btcrenaud", "Dungeon") deklarieren, und nicht für ein gewöhnliches Bukkit-Plugin.
Ein Plugin, das Dungeon-Daten braucht, sollte die Dungeon-Platzhalter und -Einträge verwenden oder über eine kleine Typewriter-Erweiterung gehen, die als Brücke dient.
Behalten Sie weder eine DungeonRunView noch einen Event-Listener über einen Reload der Dungeon-Erweiterung hinweg. Lesen Sie eine frische Ansicht und registrieren Sie Ihre Listener neu, wenn Ihre Erweiterung geladen wird.
Nur zwei Pakete sind öffentlich und stabil:
btcrenaud.dungeons.apibtcrenaud.dungeons.api.event
Alles andere ist intern und kann sich in jeder Version ohne Vorankündigung ändern.
Einrichtung
Fügen Sie in der build.gradle.kts Ihrer Erweiterung Dungeon zum Compile-Classpath hinzu und deklarieren Sie sie als Typewriter-Abhängigkeit, so wie Dungeon es selbst für eine andere Erweiterung tut:
dependencies {
compileOnly(files("libs/TypeWriter-DungeonExtension.jar"))
}
typewriter {
namespace = "yournamespace"
extension {
name = "YourExtension"
// ...
dependencies {
dependency("btcrenaud", "Dungeon")
}
}
}
DungeonApi
DungeonApi ist ein Kotlin-object. Alle Funktionen können von jedem Thread aus aufgerufen werden.
Lesezugriffe
| Funktion | Liefert |
|---|---|
runOf(player: UUID) | Die DungeonRunView des Durchlaufs, in dem der Spieler ist, in jedem Zustand (ein endender Durchlauf listet seine Mitglieder bis zur Freigabe weiter auf), oder null. |
run(runId: String) | Den Durchlauf runId oder null, wenn er nicht mehr existiert. Eine Durchlauf-ID ist eindeutig und wird nie wiederverwendet. |
runs() | Alle Durchläufe des Servers im Moment des Aufrufs, in beliebiger Reihenfolge. Jede Ansicht hat ihren eigenen Zeitpunkt; die Liste ist über Durchläufe hinweg nicht atomar. |
runsOf(dungeonId: String) | Die Durchläufe des Dungeons, dessen dungeon_instance-Eintrags-ID dungeonId ist. Leer bei unbekanntem Dungeon. |
isInRun(player: UUID) | Ob der Spieler in einem Durchlauf ist, der vorbereitet wird oder aktiv ist: dieselbe Frage, die der Dungeon stellt, bevor er jemanden einen Durchlauf starten oder betreten lässt. false bei einem Spieler, dessen Durchlauf bereits endet. |
roomsOf(player: UUID) | Die IDs der Räume, in denen der Spieler steht, so wie der Dungeon sie zuletzt gesehen hat: die dungeon_room-Eintrags-IDs eines festen Dungeons, generated:<n> bei einem generierten. Leer, wenn der Spieler in keinem Durchlauf oder in einem Durchlauf ohne Räume ist. |
aliveMobs(runId: String, mobEntryId: String? = null) | Wie viele Dungeon-Mobs des Durchlaufs jetzt leben, bei Angabe nur die vom dungeon_mob_spawn-Eintrag mobEntryId erzeugten. 0 bei unbekanntem Durchlauf. |
Die Obergrenze lebender Mobs kann einige zurückhalten, und direkt nach einem Reload werden die Mobs Chunk für Chunk neu gezählt. Um zu wissen, dass ein Bereich geräumt wurde, hören Sie DungeonMobsClearedEvent ab.
Aktionen
Jede Aktion liefert ein DungeonActionResult:
| Ergebnis | Bedeutung |
|---|---|
Done | Die Aktion wurde angenommen. Manche Aktionen enden in einem späteren Tick; das passende Ereignis sagt, wann. |
NotFound(what) | Was der Aufruf benennt, existiert nicht (mehr); what sagt, welches Ding fehlt. |
Refused(reason) | Der Aufruf benennt reale Dinge, aber der Dungeon lehnt es jetzt ab. Nichts wurde geändert. |
Die Texte von what und reason sind englisch und für Entwickler gedacht, die ein Log lesen; Spielern werden sie nie angezeigt.
| Aktion | Was sie tut | Refused, wenn |
|---|---|---|
endRun(runId) | Beendet den Durchlauf wie ein Administrator (dungeon_close): Die Spieler werden nach Hause geschickt, die Welt wird freigegeben. Die Endsequenz läuft in einem späteren Tick; DungeonRunEndEvent (Grund ADMIN) meldet ihren Beginn. | Der Durchlauf wird noch vorbereitet (ein Start wird nicht von hier abgebrochen: hören Sie stattdessen DungeonRunPreLaunchEvent ab) oder ein anderer Aufruf besitzt sein Ende bereits: ein Durchlauf endet nur einmal. NotFound("run") bei unbekanntem Durchlauf. |
leaveRun(player) | Nimmt den Spieler aus seinem Durchlauf, wie die Aktion dungeon_leave: Er wird dorthin zurückgeschickt, woher er kam. War er der Letzte, endet der Durchlauf direkt danach. DungeonPlayerLeaveRunEvent meldet, dass er gegangen ist. | Der Durchlauf wird noch vorbereitet (der Start besitzt seine Mitglieder, bis er aktiv ist) oder endet bereits (seine Endsequenz schickt die Mitglieder nach Hause). NotFound, wenn der Spieler in keinem Durchlauf ist. |
placeChest(runId, chestEntryId) | Platziert die Beutetruhe des dungeon_loot_chest-Eintrags im Durchlauf, wie ein Loot Chest Trigger, aber ohne dessen Ton, Partikel oder Nachricht. Eine bereits platzierte Truhe zu platzieren ändert nichts. | Der Eintrag ist nicht im manuellen Modus, gehört zu einem anderen Dungeon als der Durchlauf, der Durchlauf ist nicht aktiv oder die Welt des Durchlaufs ist nicht geladen. NotFound bei unbekanntem Durchlauf ("run") oder unbekanntem Eintrag ("loot chest entry"). |
Threads
- Lesezugriffe sind von jedem Thread aus sicher: Sie lesen einen unveränderlichen Schnappschuss der Registry, und
aliveMobszählt ohne Sperre. - Aktionen sind ebenfalls von jedem Thread aus sicher und warten nie auf das Spiel: Was auf dem Server-Thread laufen muss, wird den Schedulern des Servers übergeben.
leaveRunvom Server-Thread aus aufgerufen: Der Spieler ist gegangen, wennDonezurückkommt. Von einem anderen Thread aus aufgerufen: Der Abgang läuft im nächsten Tick des Server-Threads, undDonebedeutet angenommen; das Ereignis meldet, dass er stattfand.placeChest:Donebedeutet angenommen. Die Truhe erscheint im nächsten Tick auf dem Thread, dem ihr Block gehört;DungeonChestPlaceEventmeldet, dass sie steht. Das kann ausbleiben (ein Block, der nicht ersetzt werden kann), was im Log vermerkt wird.
DungeonRunView
Ein zu einem Zeitpunkt eingefrorener Durchlauf. Es ist eine Kopie, die sich nach ihrer Erstellung nie ändert: Sie kann aufbewahrt, verglichen und von jedem Thread gelesen werden; fragen Sie DungeonApi erneut nach einer frischen. Alle Felder stammen aus einem einzigen Lesen der Registry, daher stimmen state und members immer überein.
| Feld | Typ | Bedeutung |
|---|---|---|
runId | String | Eindeutige ID des Durchlaufs; sie verschwindet mit ihm. |
dungeonId | String | ID des dungeon_instance-Eintrags, den der Durchlauf spielt. |
dungeonName | String | Anzeigename dieses Eintrags, wie der Autor ihn geschrieben hat (kann leer sein). |
state | DungeonRunState | Wo der Durchlauf in seinem Leben steht. |
members | List<UUID> | Die Spieler im Durchlauf in der Reihenfolge ihres Eintreffens: Der erste ist der Anführer. Online oder nicht (ein gehaltener Platz zählt). |
worldName | String? | Die Welt, in der der Durchlauf gespielt wird; null, bis sie bereit ist. |
startedAtEpochMs | Long | Wann der Platz des Durchlaufs reserviert wurde, in Epoch-Millisekunden. |
activatedAtEpochMs | Long? | Wann die Spieler hineingelassen wurden; null während der Vorbereitung und bei einem nach einem Reload übernommenen Durchlauf. |
deadlineEpochMs | Long? | Wann der Durchlauf enden muss; null bei einem Durchlauf ohne Zeitlimit (oder noch nicht aktiv). |
seed | Long? | Der Seed, aus dem der Dungeon entstand, nur bei einem generierten Dungeon; null bei einem festen. Ihn erneut zu verwenden ergibt denselben Dungeon. |
generated | Boolean | Berechnete Eigenschaft: true, wenn der Dungeon von einem Generator erzeugt wurde (er hat einen seed). |
DungeonRunState
| Wert | Bedeutung |
|---|---|
PREPARING | Der Platz ist reserviert und die Welt wird vorbereitet; die Spieler sind noch nicht drin. |
ACTIVE | Die Spieler sind drin und der Dungeon wird gespielt. |
ENDING | Der Durchlauf ist vorbei oder wird geschlossen: Die Mitglieder werden nach Hause geschickt und die Welt freigegeben. Deckt auch den Moment zwischen dem Ende dieser Sequenz und dem Verschwinden des Durchlaufs ab sowie einen Start, der nach seiner Reservierung scheiterte. |
Dieses Enum gehört zur API und folgt nicht der internen Zustandsmaschine: Die Plattform kann einen internen Zustand hinzufügen, ohne Ihr when zu brechen.
DungeonEndReason
| Wert | Bedeutung |
|---|---|
COMPLETED | Der Dungeon wurde abgeschlossen (dungeon_complete). |
TIMEOUT | Das Zeitlimit des Dungeons ist abgelaufen. |
ABANDONED | Alle Spieler sind gegangen. |
ADMIN | Ein Administrator hat ihn geschlossen (dungeon_close, der Schließen-Befehl, DungeonApi.endRun). |
DungeonDeparture
| Wert | Bedeutung |
|---|---|
SENT_HOME | Der Spieler ging absichtlich (Verlassen-Aktion oder -Befehl, DungeonApi.leaveRun) und wird dorthin zurückteleportiert, woher er kam. |
DISCONNECTED | Er hat die Verbindung getrennt (oder seine Wiederverbindungsfrist ist abgelaufen): Er wird bei seinem nächsten Beitritt dorthin zurückgebracht, woher er kam. |
LEFT_THE_WORLD | Er hat die Welt des Durchlaufs aus eigener Kraft verlassen, daher gibt es keinen Ort, von dem aus man ihn zurückschicken könnte. |
Ereignisse
Alle Ereignisse sind Bukkit-Ereignisse des Pakets btcrenaud.dungeons.api.event. Hören Sie das konkrete Ereignis ab, das Sie brauchen.
| Ereignis | Abbrechbar | Wann | Felder |
|---|---|---|---|
DungeonRunPreLaunchEvent | Ja | Eine Gruppe will einen Dungeon starten: Der Start hat seine eigenen Prüfungen (Gruppengröße, Kriterien, Eintrittskosten ...) bestanden, und noch nichts ist reserviert, genommen oder erstellt. | dungeonId: String, members: List<UUID> (nur Online-Spieler, in der angegebenen Reihenfolge), forced: Boolean (ein Administrator hat den Start erzwungen), seed: Long? (der gewünschte Seed; null, wenn der Start seinen eigenen zieht, oder bei einem festen Dungeon) |
DungeonRunStartEvent | Nein | Ein Durchlauf hat begonnen: Er ist ACTIVE und seine Spieler sind drin, nachdem die onStart-Trigger und on_dungeon_start-Ereignisse liefen. | run |
DungeonRunEndEvent | Nein | Ein Durchlauf endet: sobald die Endsequenz beginnt und die Statistiken ausgezahlt sind, vor den onEnd-Triggern, dem Aufräumen von Truhen und Mobs und der Rückkehr der Spieler. | run (Zustand ENDING, Mitglieder, die in diesem Moment noch drin sind), reason: DungeonEndReason |
DungeonPlayerLeaveRunEvent | Nein | Ein Spieler hat einen Durchlauf verlassen, egal wie. Direkt nachdem der Spieler aus dem Durchlauf entfernt wurde. | run (listet den Spieler nicht mehr auf), player: UUID (kann offline sein), departure: DungeonDeparture |
DungeonRoomEnterEvent | Nein | Ein Mitglied hat einen Raum betreten, auch wenn das enterOnce des Raums seine Trigger schon verbraucht hat. | run, player: Player, roomId: String, templateId: String? |
DungeonRoomLeaveEvent | Nein | Ein Mitglied hat einen Raum verlassen, auch wenn leaveOnce seine Trigger schon verbraucht hat. Folgt auch dem Abgang eines Spielers aus dem Durchlauf (oder seiner Welt), während er in einem Raum stand. | run, player: Player, roomId: String, templateId: String? |
DungeonMobDeathEvent | Nein | Ein Mob des Dungeons ist gestorben, während sein Durchlauf ACTIVE war. Teile eines sich teilenden Mobs (Slimes) und andere Nachkommen lösen nichts aus. | run, entity: LivingEntity, killer: Player? (null, wenn etwas anderes ihn tötete), mobEntryId: String |
DungeonMobsClearedEvent | Nein | Ein Bereich wurde geräumt: Sein letzter Mob der letzten Welle ist gestorben. Einmal pro Räumung, nicht einmal pro Mitglied. | run, mobEntryId: String |
DungeonChestPlaceEvent | Nein | Eine Beutetruhe wurde platziert, einmal pro Truhe, bei ihrer ersten Registrierung. Eine nach einem /tw reload übernommene Truhe löst nichts aus. | run, chestId: String, chestEntryId: String, location: Location (eine Kopie der Blockposition) |
DungeonChestOpenEvent | Ja | Ein Mitglied will eine Beutetruhe öffnen (oder auslösen), per Klick auf ihren Block oder ihren NPC. | run, player: Player, chestId: String, chestEntryId: String, location: Location |
Hinweise zu den Feldern:
roomIdist bei einem festen Dungeon die ID desdungeon_room-Eintrags und bei einem Raum eines generierten Dungeonsgenerated:<n>; dessentemplateIdist die ID der Raumvorlage, aus der er entstand (nullbei einem festen Dungeon).chestIdbenennt genau diese Truhe im Durchlauf: die Eintrags-ID für ihren Hauptplatz,<entry>#<n>für einen zusätzlichen Ort,<entry>@<room>.<marker>für die Truhe eines Markers.chestEntryIdist in jedem Fall die ID desdungeon_loot_chest-Eintrags.DungeonRunPreLaunchEventhat keineDungeonRunView: Der Durchlauf existiert noch nicht. Ein Start, der dieses Ereignis passiert hat, kann später noch scheitern; nurDungeonRunStartEventsagt, dass ein Durchlauf wirklich begann. Ein Start, der scheiterte, bevor der DurchlaufACTIVEwurde, löst kein Endereignis aus.
Abbrechen
DungeonRunPreLaunchEvent: Ein abgebrochener Start wird spurlos abgelehnt (kein Platz reserviert, keine Kosten abgezogen, keine Welt erstellt). Den Spielern wird standardmäßig nichts mitgeteilt, weil das Snippetcontent.dungeon_launch.cancelledleer ist: Die abbrechende Erweiterung muss den Spielern selbst eine Erklärung geben, oder der Serverbetreiber füllt dieses Snippet. Ein Start aus einer Lobby bringt die Gruppe zurück in die Lobby.DungeonChestOpenEvent: Ein abgebrochener Klick wird verbraucht. Die Truhe öffnet sich nicht, nichts wird ausgewürfelt, keine Aktion läuft, keine Statistik zählt, und die Truhe bleibt, wie sie war, sodass der Spieler es erneut versuchen kann. Erklären Sie dem Spieler selbst, warum; standardmäßig wird nichts gesagt.
Gemeinsamer Vertrag aller Ereignisse
- Ein Ereignis wird einmal pro Tatsache ausgelöst, nie einmal pro Mitglied: ein
DungeonRunEndEventpro Durchlauf, egal wie viele Spieler er hatte. - Es wird auf dem Thread ausgelöst, der die Tatsache erzeugt hat: auf Paper der Server-Thread.
- Ein Listener, der eine Exception wirft, wird vom Server geloggt und ignoriert; er kann nie einen Durchlauf zerstören. Ein Listener darf auch nicht blockieren, da er inline mitten in der eigenen Sequenz des Durchlaufs läuft.
- Die Basisklasse
DungeonEventabzuhören wird nicht unterstützt: Bukkit weigert sich, sie zu registrieren. Hören Sie die konkreten Ereignisse ab. - Ereignisse beschreiben das aktuelle Laden der Erweiterung. Ein
/tw reloadlöst für die Durchläufe, die er übernimmt, kein End- oder Verlassen-Ereignis aus: Sie werden stillschweigend neu übernommen, und ihre späteren Tatsachen werden normal ausgelöst. - Verlässt ein Spieler einen Durchlauf, während er in einem Raum steht, kommt
DungeonRoomLeaveEventvor demDungeonPlayerLeaveRunEventdesselben Abgangs, und dessenrun.memberslistet den Spieler nicht mehr auf.
Beispiele
Ein Listener, der einen Start unter einer Bedingung abbricht und die Spieler informiert:
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
// Sonst erklärt es niemand: Das Standard-Snippet ist leer.
event.members
.mapNotNull { Bukkit.getPlayer(it) }
.forEach { it.sendMessage(Component.text("Dungeons sind wegen Wartungsarbeiten geschlossen.")) }
}
}
Den Durchlauf eines Spielers lesen:
import btcrenaud.dungeons.api.DungeonApi
import java.util.UUID
fun describeRun(player: UUID): String {
val run = DungeonApi.runOf(player) ?: return "Nicht in einem Dungeon."
return "${run.dungeonName} (${run.state}): ${run.members.size} Spieler"
}