EternalTDのレベルとマップ
EternalTDにおける「レベル」とは、plugins/EternalTD/levels/ 内のYAML設定と plugins/EternalTD/worlds/ 内のテンプレートワールドフォルダのペアです。プレイヤーが参加すると、EternalTDはテンプレートワールドをサーバーのワールドコンテナにクローンし、そのクローンされたコピー内でセッションを実行します。
レベル設定フィールド
| フィールド | 型 | デフォルト | 注記 |
|---|---|---|---|
isEnabled | bool | true | 無効化されたレベルはロード中にスキップされます |
levelName | string | null | メッセージ、スコアボード、NPCメニューに表示される表示名 |
levelDescription | string list | [] | NPCメニューに表示される行。$highscoreWave と $highscorePlayer のプレースホルダをサポート |
worldName | string | null | plugins/EternalTD/worlds/ 配下のテンプレートフォルダ名 |
startLocation | string list | null | モブがスポーンするシリアライズされた位置のリスト |
endLocation | string list | null | モブが向かう先(「赤い」タイル)のシリアライズされた位置のリスト |
levelLocations | string list | null | レベル内のすべての歩行可能なグリッドマス。パッケージに含まれています。カスタムマップでは /etd register <level> が床の選択範囲から書き込むほか、手動で記入することもできます |
wavesConfigFile | string | 必須 | リンクされたウェーブ設定(waves/<name>.yml)のファイル名 |
highscoreWave | int | 0 | このレベルで到達した最高ウェーブ |
highscorePlayerName | string | "No one" | スコアを記録したプレイヤーの表示名 |
environment | enum | NORMAL | クローンされたワールドがロードされる際に使用されるワールド環境 |
全体を通じて使用されるグリッドサイズは、論理マスあたり 3 ブロックです(コード内の定数 GRID_SIZE)。
waveCount は使用されません。古いファイルにこのキーがあってもウェーブ数は決まりません。EternalTDはリンクされたウェーブ設定を使います。
位置文字列の形式
EternalTDの位置は、以下の形式のカンマ区切り文字列としてシリアライズされます:
worldName,x,y,z,pitch,yaw
4項目の worldName,x,y,z も受け付け、pitchとyawはゼロになります。ワールドを含まない5項目の x,y,z,pitch,yaw も受け付けます。セッションのグリッド・開始・終了位置では、指定されたワールドをインスタンスのワールドに置き換えます。ハブとNPCの位置では、指定したワールドが読み込まれている必要があります。
ダウンロードしたパッケージには通常これらの値が含まれます。カスタムマップの levelLocations には、プレイ可能な3×3の床マスごとに、中央の床ブロックの整数座標を3ブロック間隔で1つずつ指定します。ローダーがXとZに0.5を加えてワールド内の中心に合わせます。/etd register <level> は床の選択範囲からこの一覧を書き込みます(マップ作成ワークフローを参照)。手動で記入することもできます。
ワールドのライフサイクル
プレイヤーがレベルに参加するとき:
- EternalTDは
plugins/EternalTD/worlds/の直下のフォルダからworldNameを探します。名前に.を含むものは除外されます。テンプレートがない、ウェーブ設定を利用できない、名前が安全でない場合は拒否します。levelLocations、startLocation、endLocationのいずれかが存在しない、空である、または解析できない項目を含むレベルも拒否します:「This level has missing or invalid floor, entrance, or exit coordinates. Nothing was started.」 - プレイヤーの最初のワールド準備要求が完了していない間は、2つ目の要求を拒否します。他のNightbreakプラグインのマッチを含め、すでにマッチに参加しているプレイヤーも拒否します(「You are already in a match!」)。準備中の要求は共有マッチシステム上でプレイヤーを予約するため、完了するまで他のプラグインのマッチはそのプレイヤーを受け付けません。切断するか、10分経ってもワールドの準備が整わない場合は要求がキャンセルされ、途中までのコピーは削除されます。
- 次の空き数値サフィックス(
<worldName>_0、<worldName>_1、...)を予約します。予約は同期化されており、予約済みだがまだディスク上に存在しない名前も記憶するため、2人のプレイヤーが同時に参加しても同じインスタンス名で衝突することはありません。 - テンプレートはメインスレッド外でワールドコンテナへコピーされます。ワールドの読み込みとセッションの初期化はメインスレッドで行います。
- クローンされたワールドはMagmaCoreの
TemporaryWorldManagerを介して一時的なvoidワールドとしてロードされるため、Paper 26.1+ の移行は隔離され、欠けているチャンクは新しく生成された地形ではなくvoidとして戻ってきます。 - EternalTDがグリッドを検証し(セッション開始時の検証を参照)、
InstanceProtectorがEternalTDの保護ルールを適用します。続いてマッチシステムがプレイヤーを受け入れます。復元記録を保存し、次のティックで新しいワールドのスポーン地点へテレポートさせ、マッチ用アイテムを配布します。別のプラグインがこのテレポートを拒否した場合、プレイヤーはマッチから外れ、ランは終了します。「Created your world! We're ready to go.」が表示されるまで開始しないでください。それより前の「Joined level!」だけではセッションの成功を確認できません。
テンプレートの要件
EternalTDはコピー作成前にルートを、コピー中に内部の各項目を検証します。内部の項目が無効なら途中のコピーをロールバックします。次の場合はテンプレートが拒否されます:
- ディレクトリでない、またはシンボリックリンクである
- ルートに
level.datがない - 内部のどこかにシンボリックリンクを含んでいる
.eternaltd-instanceという名前のファイルを含んでいる(この名前は予約されています。後述参照)
コピーに失敗するとロールバックを開始し、エラーメッセージを表示します。クリーンアップ自体に失敗した場合はコンソールを確認してください。メッセージだけでは全ファイルの削除を確認できません。
インスタンス所有権マーカー
各コピーには、テンプレート名とインスタンス名を記録した .eternaltd-instance が作られます。EternalTDは、古いインスタンスを削除する前と、検証に失敗したコピー済みワールドを削除する前にこのマーカーを確認します。途中までのコピーはコピーに失敗した時点で削除され、作成されたセッションのワールドはセッション終了時にマッチシステムが削除します(後述)。
クラッシュで残った古いインスタンスはレベルの読み込み時に整理されますが、その場合でも有効なマーカーを持ち、現在読み込まれていないフォルダに限られます。
セッション開始時の検証
セッションがプレイ可能になるには、次の条件をすべて満たす必要があります:
levelLocations、startLocation、endLocationがすべて存在し、空でないこと — そうでなければ「This level doesn't have a complete play area, start, and end configuration!」- プレイエリア自体が有効なグリッドとして解析できること — そうでなければ「This level's play area is invalid!」
- すべてのスタートタイルから、いずれかのエンドタイルへ地上経路と空中経路の両方が存在すること — そうでなければ「This level does not have complete land and air paths from every start to an end!」
これらのチェックはコピーしたワールドの読み込み後、プレイヤーを受け入れる前に行われるため、失敗してもプレイヤー自身の状態は変更されません。その後EternalTDは一時ワールドを削除し、「Failed to start the level. Your player and the temporary world were rolled back.」と報告します。復元記録の保存後に受け入れ自体が失敗した場合は、その記録がすぐに復元されます。
セッションが終了すると:
- 残っているタワーがすべて撤去されます(撤去中に例外をスローしたタワーはログに記録されてスキップされ、残りが取り残されることはありません)。
- プレイヤーの復元記録が復元され、レベルに入る直前にいた場所へ戻されます。そのワールドが読み込まれていない場合はメインワールドのスポーンへ移動します。
- ワールドの保護ルールが解除されます。
eternaltd.match.bypassを持つスタッフなど、コピーされたワールドにまだ残っている他のプレイヤーはconfig.ymlのハブスポーンへ、ハブが未設定の場合はメインワールドのスポーンへ送られます。 - プレイエリア上に確保されていたチャンクチケットが解放されます(経路の検証を参照)。
- クリーンアップは両方の保存形式に対してアンロードと削除を要求します。アンロードが拒否されれば削除しません。プラグインが有効な間、削除はアンロードの5秒後に非同期で行われます。整理前に再び読み込まれたワールドもスキップし、削除が不完全な場合はログに記録します。
- 終了処理はべき等です — 2回目の
end()(例えば/etd quitの直後の/etd hub)は何もしません。
インスタンス保護ルール
レベルがアクティブな間、クローンされたワールドには以下のルールが適用されます:
- 爆発を無効化
- 液体の流れを無効化
- エリトラを無効化
- 飛行切り替えを防止
- フレンドリーファイアを防止
- バニラのモブスポーンを防止
マッチシステムもワールドを保護します。ラン中のプレイヤーは、EternalTD自身による移動以外では、ワールドへの出入りもワールド内でのテレポートもできません。また、eternaltd.match.bypass を持たない他のプレイヤーはテレポートで入れません。マッチのルールを参照してください。
マップ作成ワークフロー
テンプレートを編集元として使い、グリッドをレベルYAMLに記入してください:
- 別のワールドまたはシングルプレイでマップを編集・保存し、テンプレートを
plugins/EternalTD/worlds/<worldName>/に配置します。試合用のコピーを編集してもテンプレートは更新されません。 - レベルYAMLを作り、読み込み済みのウェーブ設定をリンクして
levelLocations、startLocation、endLocationを記入します。グリッドには床の中央ブロック、開始・終了にはそのマスに対応する座標を使います。 - 編集用ワールドでゲーム内ツールを使う場合は、
/etd reloadで設定を読み込み、/etd selectfloorを実行します。左クリックで角1、右クリックで角2を設定します。両角は同じワールド・同じ床のYに置き、両端を含むX/Zの長さを3の倍数にしてください。 - 代わりに
/etd selectfloorcoordinates <x1> <y1> <z1> <x2> <y2> <z2>を使うと、現在のワールドで両角を指定できます。入力した各Yから1を引くため、床のYに1を足した値を渡してください。 /etd showselection <level>は、選択範囲で登録される各床マスの上に、自分にだけ見えるEND_RODパーティクルを1つずつ表示します。何も保存しません。/etd register <level> startまたはendの実行中は選択を有効にしておきます。プレイヤーの整数ブロック座標を追加してすぐ保存しますが、Yから1を引く処理もグリッド上かの確認もありません。先に両方のリストを初期化してください。YAMLで直接編集することもできます。- これらの登録が終わったら、
/etd register <level>を実行します。選択範囲からlevelLocationsを書き込んで既存のリストを置き換え、レベルファイルを保存して選択を解除します。選択モードを無効にしても選択範囲は破棄されます。 - 編集済みテンプレートを保存してリロードし、NPCメニューまたは
/etd join <level>から参加します。最後の準備完了メッセージを待ち、プレイしてマップを確認してください。
選択の確認処理は次の式で長さを計算し、小数部分を切り捨てます:
size = (abs(corner1 - corner2) + 1) / 3
角の順番は関係ありません。プレイヤーの現在のワールドで、角1のYの高さにおいて、小さい方のX/Zの角に1を加えた場所から3ブロックごとに中央ブロックを確認します。中央の床ブロックが通過可能、またはその真上が通過不可なら、そのマスを除外します。3×3の全ブロックを検証するわけではありません。
経路の検証
EternalTDは、各スタートタイルについて、いずれかのエンドタイルへ至る最もコストの低いA*経路をキャッシュします — 地上経路と空中経路をそれぞれ1つずつです。エンドタイルが複数ある場合は完成した経路をコストで比較して最短のものを残すため、出口が複数あるマップでは、最初に見つかったものではなく最も近い到達可能な出口へ敵が誘導されます。
地上のキャッシュは、セッション開始時、タワーが設置されたとき、タワーが売却されたときに再構築されます。空中のキャッシュは1度だけ、最初の再構築時に計算されます。空中経路はタワーを完全に無視するため、プレイヤーが何を建てても無効化されないからです。
Air属性の敵は別の空中経路を使用します。これはタワーによる遮断を完全に無視し、代わりに空中オフセット(設定された経路の4ブロック上)を辿ります。
プレイエリアの読み込み時には、グリッドマスを含むすべてのチャンクが強制ロードされるため、長いランの途中で未読み込みのチャンクによって停止することはありません。これらのチャンクチケットは、インスタンスワールドが削除される直前、セッション終了時に解放されます。
このキャッシュは、そもそもレベルがプレイ可能かどうかの判定にも使われます。いずれかのスタートタイルで地上経路か空中経路のどちらかが欠けている場合、敵がゴールできない実行を始めるのではなく、参加時点でセッションが拒否されます。同じチェックはタワー設置時にも実行されるため、スタートタイルを塞いでしまうタワーは拒否され、ゴールドも消費されません。
開始・終了位置はX/Zからグリッドマスを探し、中心との完全一致やYの比較は行いません。対応するマスがない項目は警告付きでスキップします。警告は中心のずれを示唆しますが、目的のマスが levelLocations に含まれているかも確認してください。
解決されたスタートタイルとエンドタイルはすべて建設不可としてマークされるため、プレイヤーがスポーンや出口の上に建設して塞ぐことはできません。これらのタイルは設置ハイライトの下で水色に表示されます。
NPCとレベルメニュー
plugins/EternalTD/npcs/ の設定は村人とレベルをリンクします。右クリックで9スロットのメニューが開き、0、2、4、6、8に項目を配置します。設定されたレベルIDの最初の5件のみを対象とし、存在しないIDは空きスロットになり、それ以降のIDは警告付きで除外します。参照先に読み込み済みレベルが1つもないNPCは作られません。緑色のステンドグラス板を左クリックすると対応するレベルへの参加を要求します。
| フィールド | 型 | デフォルト | 注記 |
|---|---|---|---|
isEnabled | bool | true | 無効化されたNPCはスキップされます |
levelIDs | string list | 必須 | このNPCが提供するレベルのファイル名 |
location | string | null | 標準の worldName,x,y,z,pitch,yaw 形式のスポーン位置 |
name | string | カスタム設定では null | NPCの表示名。プリメイド定義には値がある場合があります |
difficulty | string | カスタム設定では null | NPCの上のラベル。明示的に設定してください |
disguise | string | null | LibsDisguises記述子(例: custom:etd_tutorial_npc) |
customDisguiseData | string | null | LibsDisguisesの追加コマンドデータ — 通常は長いプレイヤースキン文字列 |
NPCのスポーンは設定の構築から4秒後に予定され、指定位置のワールドが読み込み済みである必要があります。村人は無敵・AI無効・永続化され、EternalTDのNPCキーが付きます。LibsDisguisesが有効で両方の変装フィールドが設定されていれば変装を試みますが、記述子が無効な場合は通常の村人のままになることがあります。
difficulty ラベルを持つ浮遊するアーマースタンドが、NPCの2.3ブロック上にスポーンされます。
スポーン動作
DefaultConfig は、ハブワールドでのプレイヤーの管理方法を制御します:
setupDone— 初回セットアップガイダンスが完了したかどうかを追跡するフラグ(デフォルトはfalse)。spawnLocations— デフォルトはetd_spawn,0,65,0,0,0で、常に設定ファイルに書き込まれます。解決されて使用されるのは、etd_spawnワールドが存在する場合だけです。manageSpawn— デフォルトはtrue。有効で移動先が読み込まれている場合、ログインから1 tick後にスポーンへのテレポートを要求します。playerGuide— ゲーム内ガイドブックのテキスト。nightbreak.autoDownloadPluginUpdates— 共有のMagmaCore設定(デフォルトはfalse)。有効な場合、プラグインとコンテンツのアップデートが起動時に自動的にダウンロードされます。nightbreak.applyPluginUpdatesWithoutRestart(共有のMagmaCore設定。デフォルトはnever。ほかにwhen-emptyまたはimmediately)は、ダウンロードされたプラグインアップデートを再起動なしで適用するタイミングを制御します。セットアップを参照してください。
初期化完了時には、接続済みのプレイヤーにも同じテレポートを要求します。ただし、すでに移動先のワールドにいる場合は除きます。この処理にも manageSpawn と読み込み済みの移動先が必要です。ログイン時と初期化完了時の処理はテレポート結果を確認しないため、別のプラグインによって移動が阻止されることがあります。