メインコンテンツまでスキップ

開発者向け API

Dungeon は小さな API を公開しており、他の Typewriter 拡張がサーバーのランを読み取り、操作し、そこで起きることに反応できます。このページは開発者向けです。ダンジョンを作るだけなら必要ありません。

利用できる人​

Typewriter はすべての拡張を、/tw reload のたびに作り直される専用の 1 つのクラスローダーに読み込みます。そのため、この API のクラスは dependency("btcrenaud", "Dungeon") を宣言した他の Typewriter 拡張からは見えますが、通常の Bukkit プラグインからは見えません。

ダンジョンのデータが必要なプラグインは、Dungeon のプレースホルダーとエントリーを使うか、橋渡しをする小さな Typewriter 拡張を経由してください。

リロードをまたいで何も保持しない

Dungeon 拡張のリロードをまたいで DungeonRunView やイベントリスナーを保持しないでください。新しいビューを読み直し、自分の拡張が読み込まれるときにリスナーを再登録してください。

公開され安定しているのは次の 2 つのパッケージだけです。

  • btcrenaud.dungeons.api
  • btcrenaud.dungeons.api.event

それ以外はすべて内部用で、どのリリースでも予告なく変更される可能性があります。

セットアップ​

自分の拡張の build.gradle.kts で、Dungeon をコンパイルクラスパスに追加し、Dungeon 自身が別の拡張に対して行っているのと同じように Typewriter の依存関係として宣言します。

dependencies {
compileOnly(files("libs/TypeWriter-DungeonExtension.jar"))
}

typewriter {
namespace = "yournamespace"

extension {
name = "YourExtension"
// ...

dependencies {
dependency("btcrenaud", "Dungeon")
}
}
}

DungeonApi​

DungeonApi は Kotlin の object です。すべての関数は任意のスレッドから呼び出せます。

読み取り​

関数戻り値
runOf(player: UUID)プレイヤーが参加しているランの DungeonRunView(状態は問いません。終了処理中のランも解放されるまではメンバーを列挙します)。参加していなければ null。
run(runId: String)ラン runId。すでに存在しない場合は null。ラン ID は一意で、再利用されません。
runs()現在のサーバーのすべてのラン(順序は不定)。各ビューはそれぞれの時点のもので、リストはラン間でアトミックではありません。
runsOf(dungeonId: String)dungeon_instance エントリー ID が dungeonId のダンジョンのラン。未知のダンジョンなら空。
isInRun(player: UUID)プレイヤーが準備中または進行中のランにいるかどうか。ダンジョンが開始や参加を許可する前に行うのと同じ確認です。ランがすでに終了処理中のプレイヤーでは false。
roomsOf(player: UUID)ダンジョンが最後に認識した、プレイヤーがいる部屋の ID。固定ダンジョンでは dungeon_room エントリー ID、生成ダンジョンでは generated:<n>。ランにいない、または部屋のないランにいる場合は空。
aliveMobs(runId: String, mobEntryId: String? = null)ランの生存中のダンジョンモブの数。mobEntryId を指定した場合は、その dungeon_mob_spawn エントリーが生み出したものだけ。未知のランなら 0。
生存モブが 0 でも「掃討済み」とは限らない

生存モブの上限により一部が保留されていることがあり、リロード直後はモブがチャンクごとに数え直されます。サイトが掃討されたことを知るには DungeonMobsClearedEvent を購読してください。

アクション​

すべてのアクションは DungeonActionResult を返します。

結果意味
Doneアクションは受理されました。一部のアクションは後のティックで完了します。いつ完了したかは対応するイベントが伝えます。
NotFound(what)呼び出しが指したものが存在しません(または、もう存在しません)。what が何が欠けているかを示します。
Refused(reason)呼び出しが指したものは実在しますが、ダンジョンは今はそれを行いません。何も変更されません。

what と reason の文章は英語で、ログを読む開発者向けです。プレイヤーには決して表示されません。

アクション内容Refused になる場合
endRun(runId)管理者が行うのと同様にランを終了します(dungeon_close)。プレイヤーは帰され、ワールドは解放されます。終了シーケンスは後のティックで実行され、DungeonRunEndEvent(理由 ADMIN)がその開始を知らせます。ランがまだ準備中(開始はここからは取り消せません。代わりに DungeonRunPreLaunchEvent を購読してください)、または別の呼び出しがすでにその終了を所有している場合。ランは一度しか終了しません。未知のランは NotFound("run")。
leaveRun(player)dungeon_leave アクションと同様に、プレイヤーを参加中のランから外します。元いた場所に戻されます。最後の 1 人だった場合、ランはその直後に終了します。DungeonPlayerLeaveRunEvent が退出を知らせます。ランがまだ準備中(開始処理が、アクティブになるまでメンバーを所有します)、またはすでに終了処理中(終了シーケンスがメンバーを帰します)の場合。プレイヤーがどのランにもいなければ NotFound。
placeChest(runId, chestEntryId)dungeon_loot_chest エントリーのルートチェストをランに設置します。Loot Chest Trigger と同様ですが、サウンド、パーティクル、メッセージはありません。すでに設置済みのチェストを設置しても何も変わりません。エントリーが手動モードでない、ランとは別のダンジョンに属している、ランがアクティブでない、またはランのワールドが読み込まれていない場合。未知のランは NotFound("run")、未知のエントリーは NotFound("loot chest entry")。

スレッド​

  • 読み取りは任意のスレッドから安全です。レジストリの不変スナップショットを読み、aliveMobs はロックなしで数えます。
  • アクションも任意のスレッドから安全で、ゲームを待つことはありません。サーバースレッドで実行すべき処理は、サーバー自身のスケジューラーに渡されます。
  • leaveRun をサーバースレッドから呼んだ場合:Done が返った時点でプレイヤーは退出済みです。他のスレッドから呼んだ場合:退出は次のサーバースレッドのティックで実行され、Done は受理されたことを意味します。実際に起きたことはイベントが伝えます。
  • placeChest:Done は受理されたことを意味します。チェストは次のティックに、そのブロックを所有するスレッドで現れます。設置されたことは DungeonChestPlaceEvent が伝えます。置換できないブロックなどで設置されない場合があり、その際はログに記録されます。

DungeonRunView​

ある瞬間で固定されたラン。作成後は決して変化しないコピーなので、保持、比較、任意のスレッドからの読み取りができます。最新のものが必要なら DungeonApi に再度問い合わせてください。すべてのフィールドは 1 回のレジストリ読み取りから得られるため、state と members は常に一致します。

フィールド型意味
runIdStringランの一意な ID。ランとともに消えます。
dungeonIdStringランがプレイしている dungeon_instance エントリーの ID。
dungeonNameStringそのエントリーの表示名(作者が書いたとおり。空の場合もあります)。
stateDungeonRunStateランが生涯のどの段階にあるか。
membersList<UUID>ランのプレイヤーを到着順に並べたもの。先頭がリーダー。オンラインかどうかは問いません(確保されている枠も含む)。
worldNameString?ランがプレイされるワールド。準備できるまでは null。
startedAtEpochMsLongランの枠が確保された時刻(エポックミリ秒)。
activatedAtEpochMsLong?プレイヤーが入場を許可された時刻。準備中、およびリロード後に引き継がれたランでは null。
deadlineEpochMsLong?ランが終了すべき時刻。時間制限のないラン(またはまだアクティブでないラン)では null。
seedLong?ダンジョンを生成したシード。生成ダンジョンのみ。固定ダンジョンでは null。再利用すると同じダンジョンになります。
generatedBoolean算出プロパティ。ダンジョンがジェネレーターで作られた(seed がある)場合 true。

DungeonRunState​

値意味
PREPARING枠が確保され、ワールドを準備中。プレイヤーはまだ中にいません。
ACTIVEプレイヤーが中にいて、ダンジョンがプレイされています。
ENDINGランが終わった、または閉じられようとしています。メンバーは帰され、ワールドは解放されます。そのシーケンスの終了からランが消えるまでの瞬間、および枠の確保後に失敗した開始も含みます。

この列挙型は API に属し、内部のステートマシンには従いません。プラットフォームが内部状態を追加しても、利用側の when は壊れません。

DungeonEndReason​

値意味
COMPLETEDダンジョンが完了しました(dungeon_complete)。
TIMEOUTダンジョンの制限時間が切れました。
ABANDONED全プレイヤーが退出しました。
ADMIN管理者が閉じました(dungeon_close、クローズコマンド、DungeonApi.endRun)。

DungeonDeparture​

値意味
SENT_HOME自分の意思で退出し(退出アクションやコマンド、DungeonApi.leaveRun)、元いた場所にテレポートされます。
DISCONNECTED切断した(または再接続の猶予が切れた)。次回の参加時に元いた場所へ戻されます。
LEFT_THE_WORLD自力でランのワールドを離れたため、戻す元の場所がありません。

イベント​

すべてのイベントは、パッケージ btcrenaud.dungeons.api.event の Bukkit イベントです。必要な具体的なイベントを購読してください。

イベントキャンセル可発生時フィールド
DungeonRunPreLaunchEventはいグループがダンジョンを開始しようとしているとき。開始は独自の確認(グループ人数、条件、入場コストなど)を通過しており、まだ何も確保、徴収、作成されていません。dungeonId: String、members: List<UUID>(オンラインのプレイヤーのみ、渡された順)、forced: Boolean(管理者が強制開始した)、seed: Long?(要求されたシード。開始側が自分で引く場合や固定ダンジョンでは null)
DungeonRunStartEventいいえランが始まったとき。onStart トリガーと on_dungeon_start イベントの実行後で、ランは ACTIVE になりプレイヤーは中にいます。run
DungeonRunEndEventいいえランが終了するとき。終了シーケンスが始まり統計が支払われた直後、onEnd トリガー、チェストとモブの片付け、プレイヤーの帰還より前。run(状態は ENDING、その時点でまだ中にいるメンバー)、reason: DungeonEndReason
DungeonPlayerLeaveRunEventいいえ方法を問わず、プレイヤーがランを離れたとき。プレイヤーがランから外された直後。run(プレイヤーはもう列挙されない)、player: UUID(オフラインの場合あり)、departure: DungeonDeparture
DungeonRoomEnterEventいいえメンバーが部屋に入ったとき。部屋の enterOnce がすでにトリガーを使い切っていても発生します。run、player: Player、roomId: String、templateId: String?
DungeonRoomLeaveEventいいえメンバーが部屋を出たとき。leaveOnce がすでにトリガーを使い切っていても発生します。部屋にいたプレイヤーがラン(またはそのワールド)から離れた場合にも続けて発生します。run、player: Player、roomId: String、templateId: String?
DungeonMobDeathEventいいえランが ACTIVE の間にダンジョンのモブが死んだとき。分裂するモブ(スライム)の破片やその他の子孫は何も発生させません。run、entity: LivingEntity、killer: Player?(他の要因で死んだ場合は null)、mobEntryId: String
DungeonMobsClearedEventいいえサイトが掃討されたとき。最後のウェーブの最後のモブが死んだ時点。掃討 1 回につき 1 度で、メンバーごとではありません。run、mobEntryId: String
DungeonChestPlaceEventいいえルートチェストが設置されたとき。チェスト 1 つにつき 1 度、最初に登録される時点。/tw reload 後に引き継がれたチェストは何も発生させません。run、chestId: String、chestEntryId: String、location: Location(ブロック位置のコピー)
DungeonChestOpenEventはいメンバーがルートチェストのブロックまたは NPC をクリックして、開こう(または作動させよう)としているとき。run、player: Player、chestId: String、chestEntryId: String、location: Location

フィールドについての補足です。

  • roomId は固定ダンジョンでは dungeon_room エントリーの ID、生成ダンジョンの部屋では generated:<n> です。後者の templateId は、その部屋の元になったルームテンプレートの ID です(固定ダンジョンでは null)。
  • chestId はラン内のそのチェストそのものを指します。メインの設置場所ではエントリーの ID、追加の位置では <entry>#<n>、マーカーのチェストでは <entry>@<room>.<marker> です。chestEntryId はいずれの場合も dungeon_loot_chest エントリーの ID です。
  • DungeonRunPreLaunchEvent には DungeonRunView がありません。ランがまだ存在しないためです。このイベントを通過した開始も、後で失敗することがあります。ランが本当に始まったことを示すのは DungeonRunStartEvent だけです。ランが ACTIVE になる前に失敗した開始は、終了イベントを発生させません。

キャンセル​

  • DungeonRunPreLaunchEvent:キャンセルされた開始は痕跡なく拒否されます(枠の確保、コストの徴収、ワールドの作成はありません)。スニペット content.dungeon_launch.cancelled が空のため、既定ではプレイヤーに何も通知されません。キャンセルする拡張がプレイヤーに自分で説明するか、サーバー管理者がこのスニペットを埋める必要があります。ロビーからの開始では、グループはロビーに戻されます。
  • DungeonChestOpenEvent:キャンセルされたクリックは消費されます。チェストは開かず、抽選は行われず、アクションは実行されず、統計もカウントされず、チェストはそのままなのでプレイヤーは再試行できます。理由はご自身でプレイヤーに伝えてください。既定では何も伝えられません。

すべてのイベントに共通する取り決め​

  • イベントは事実 1 つにつき 1 回発生し、メンバーごとには発生しません。プレイヤーが何人いても、ラン 1 つにつき DungeonRunEndEvent は 1 つです。
  • 事実を生み出したスレッドで発生します。Paper ではサーバースレッドです。
  • 例外を投げたリスナーはサーバーがログに記録して無視します。ランを壊すことはありません。リスナーはランのシーケンスの途中でインライン実行されるため、ブロックしてもいけません。
  • 基底クラス DungeonEvent の購読はサポートされません。Bukkit が登録を拒否します。具体的なイベントを購読してください。
  • イベントは拡張の現在のロードを記述します。/tw reload は、引き継ぐランについて終了イベントも退出イベントも発生させません。それらは黙って再び引き継がれ、その後の事実は通常どおり発生します。
  • プレイヤーが部屋にいる状態でランを離れた場合、DungeonRoomLeaveEvent は同じ退出の DungeonPlayerLeaveRunEvent の前に発生し、その run.members にはもうプレイヤーが含まれません。

例​

条件付きで開始をキャンセルし、プレイヤーに知らせるリスナー:

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
// 他に説明する人はいない:既定のスニペットは空です。
event.members
.mapNotNull { Bukkit.getPlayer(it) }
.forEach { it.sendMessage(Component.text("ダンジョンはメンテナンスのため閉鎖中です。")) }
}
}

プレイヤーのランを読み取る:

import btcrenaud.dungeons.api.DungeonApi
import java.util.UUID

fun describeRun(player: UUID): String {
val run = DungeonApi.runOf(player) ?: return "ダンジョンにいません。"
return "${run.dungeonName}(${run.state}):${run.members.size} 人"
}