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 | レベル内のすべての歩行可能なグリッドマス。現在のビルドでは、これらはパッケージから供給されるか、YAMLに手動で書き込む必要があります |
wavesConfigFile | string | 必須 | リンクされたウェーブ設定(waves/<name>.yml)のファイル名 |
highscoreWave | int | 0 | このレベルで到達した最高ウェーブ |
highscorePlayerName | string | "no one" | スコアを記録したプレイヤーの表示名 |
environment | enum | NORMAL | クローンされたワールドがロードされる際に使用されるワールド環境 |
全体を通じて使用されるグリッドサイズは、論理マスあたり 3 ブロックです(コード内の定数 GRID_SIZE)。
waveCount はもう存在しません。このキーをまだ持っている古いレベルファイルは単に無視されます — プラグインはこれを読むことも書き戻すこともありません。
位置文字列の形式
EternalTDの位置は、以下の形式のカンマ区切り文字列としてシリアライズされます:
worldName,x,y,z,yaw,pitch
通常はダウンロードされたパッケージがこれらの値を提供します。カスタムマップの場合、現在の選択コマンドと register コマンドは levelLocations を保存しないため、生成されたグリッド位置をレベルYAMLに手動で書き込む必要があります。
ワールドのライフサイクル
プレイヤーがレベルに参加するとき:
- EternalTDは、このプレイヤーのコピーがすでに進行中でないことを確認します。最初のコピーがまだ進行中に2回目の
/etd joinを実行すると、「Your level is already being prepared.」で拒否されます。 plugins/EternalTD/worlds/内でworldNameに基づいてテンプレートフォルダを検索し、ワールドフォルダが存在しない、リンクされたウェーブファイルの読み込みに失敗した、またはワールド名が安全なフォルダ名でない場合は参加を拒否します。- 次の空き数値サフィックス(
<worldName>_0、<worldName>_1、...)を予約します。予約は同期化されており、予約済みだがまだディスク上に存在しない名前も記憶するため、2人のプレイヤーが同時に参加しても同じインスタンス名で衝突することはありません。 - テンプレートはメインスレッド外でサーバーのワールドコンテナへコピーされるため、大きなマップでもサーバーが停止することはありません。
- クローンされたワールドはMagmaCoreの
TemporaryWorldManagerを介して一時的なvoidワールドとしてロードされるため、Paper 26.1+ の移行は隔離され、欠けているチャンクは新しく生成された地形ではなくvoidとして戻ってきます。 - プレイヤーは新しいワールドにテレポートされます。内部の
InstanceProtectorがEternalTDの保護ルールを適用します。
テンプレートの要件
コピーは何かが書き込まれる前に検証され、次の場合はテンプレートが拒否されます:
- ディレクトリでない、またはシンボリックリンクである
- ルートに
level.datがない - 内部のどこかにシンボリックリンクを含んでいる
.eternaltd-instanceという名前のファイルを含んでいる(この名前は予約されています。後述参照)
コピーが失敗した場合はロールバックされ、レベルを開始できなかった旨がプレイヤーに伝えられます。
インスタンス所有権マーカー
すべてのクローンには、テンプレート名とインスタンス名を記録した .eternaltd-instance ファイルがルートに書き込まれます。EternalTDは、削除しようとしているインスタンスとマーカーが一致するワールドフォルダしか削除しません。これは、古い名前の衝突、手作りのフォルダ、不一致のセッションが、EternalTDの作成していないワールドを消してしまうのを防ぐガードです — マーカーがない、または一致しない場合、フォルダはそのまま残され、代わりに警告がログに出力されます。
クラッシュで残った古いインスタンスはレベルの読み込み時に整理されますが、その場合でも有効なマーカーを持ち、現在読み込まれていないフォルダに限られます。
セッション開始時の検証
セッションがプレイ可能になるのは、次のすべてが満たされた場合だけです。1つでも失敗すると実行は中止され、プレイヤーの状態(インベントリ、ゲームモード、飛行、スコアボード)が復元され、一時ワールドが削除されます:
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!」
セッションが終了すると:
- クローンされたワールド内に残っているプレイヤーは、
config.ymlのスポーン位置にテレポートされ、スポーンが設定されていない場合はキックされます。 - ワールドの保護ルールが解除され、残っているタワーがすべて撤去されます(撤去中に例外をスローしたタワーはログに記録されてスキップされ、残りが取り残されることはありません)。
- プレイエリア上に確保されていたチャンクチケットが解放されます(経路の検証を参照)。
- クローンされたワールドはアンロードされ、旧来のレイアウトとPaper 26.1+ の移行後レイアウトの両方でディスクから削除されます(
TemporaryWorldManager.permanentlyDeleteWorld)。 - 終了処理はべき等です — 2回目の
end()(例えば/etd quitの直後の/etd hub)は何もしません。
インスタンス保護ルール
レベルがアクティブな間、クローンされたワールドには以下のルールが適用されます:
- 爆発を無効化
- 液体の流れを無効化
- エリトラを無効化
- 飛行切り替えを防止
- フレンドリーファイアを防止
- バニラのモブスポーンを防止
マップ作成ワークフロー
現在のマップ作成フローはゲーム内ツールを使用します:
- テンプレートワールドフォルダを
plugins/EternalTD/worlds/<worldName>/配下に配置します。 - 対応するレベルYAMLを
plugins/EternalTD/levels/に作成またはダウンロードします。 /etd reloadを実行し、レベルワールドに手動で参加します(またはシングルプレイヤーで開いてセットアップします)。/etd selectfloorを使用し、右クリック/左クリックで2つのコーナーをマークしてプレイエリアを指定するか、/etd selectfloorcoordinates <x1> <y1> <z1> <x2> <y2> <z2>を使用して直接指定します。/etd showselection <level>を実行して、選択範囲が正しく見えるか確認します。/etd register <level>を実行して選択をクリアします。なお、現在のビルドではregisterもshowselectionも実際にはフロア領域を永続化しません —levelLocationsを保存するヘルパー(LevelsConfigFields#addLevelLocations)は定義されていますが、どのコマンドからも呼び出されません。ダウンロード済みパッケージで既に設定されていない場合は、現在levelLocationsをレベルYAMLに手動で書き込む必要があります。- スタートスポーンタイルの上に立って
/etd register <level> startを実行します。すべてのスタートタイルでこれを繰り返します(このコマンドはstartLocationに永続化されます)。 - エンドタイルの上に立って
/etd register <level> endを実行します。すべてのエンドタイルでこれを繰り返します(このコマンドはendLocationに永続化されます)。 - 再度リロードし、NPCメニューまたは
/etd join <level>経由でレベルに参加してテストします。
選択コマンドは以下の式を使用してグリッドマスを生成します:
size = abs(corner1 - corner2 + 1) / 3
マスは、フロアブロックが通過可能である場合、またはフロアの真上のブロックが通過可能でない場合にスキップされます。言い換えると、マスがプレイ可能として登録されるにはフロアがソリッドで、その上の空間が空いている必要があります — 上の空気ではなく、フロアを選択してください。
経路の検証
EternalTDは、各スタートタイルについて、いずれかのエンドタイルへ至る最もコストの低いA*経路をキャッシュします — 地上経路と空中経路をそれぞれ1つずつです。エンドタイルが複数ある場合は完成した経路をコストで比較して最短のものを残すため、出口が複数あるマップでは、最初に見つかったものではなく最も近い到達可能な出口へ敵が誘導されます。
地上のキャッシュは、セッション開始時、タワーが設置されたとき、タワーが売却されたときに再構築されます。空中のキャッシュは1度だけ、最初の再構築時に計算されます。空中経路はタワーを完全に無視するため、プレイヤーが何を建てても無効化されないからです。
Air属性の敵は別の空中経路を使用します。これはタワーによる遮断を完全に無視し、代わりに空中オフセット(設定された経路の4ブロック上)を辿ります。
プレイエリアの読み込み時には、グリッドマスを含むすべてのチャンクが強制ロードされるため、長いランの途中で未読み込みのチャンクによって停止することはありません。これらのチャンクチケットは、インスタンスワールドが削除される直前、セッション終了時に解放されます。
このキャッシュは、そもそもレベルがプレイ可能かどうかの判定にも使われます。いずれかのスタートタイルで地上経路か空中経路のどちらかが欠けている場合、敵がゴールできない実行を始めるのではなく、参加時点でセッションが拒否されます。同じチェックはタワー設置時にも実行されるため、スタートタイルを塞いでしまうタワーは拒否され、ゴールドも消費されません。
3×3のグリッド区画の中心に位置していないスタートまたはエンドの位置は、コンソール警告とともに破棄されます。これが、レベルが経路セットの不備を報告する通常の原因です。
解決されたスタートタイルとエンドタイルはすべて建設不可としてマークされるため、プレイヤーがスポーンや出口の上に建設して塞ぐことはできません。これらのタイルは設置ハイライトの下で水色に表示されます。
NPCとレベルメニュー
plugins/EternalTD/npcs/ 内のNPC設定は、村人NPCを1つ以上のレベルにリンクします。NPCを右クリックすると、各レベルが緑色のステンドグラス板として表示される9スロットのインベントリが開き、レベル名と説明がラベルとして付きます。
| フィールド | 型 | デフォルト | 注記 |
|---|---|---|---|
isEnabled | bool | true | 無効化されたNPCはスキップされます |
levelIDs | string list | 必須 | このNPCが提供するレベルのファイル名 |
location | string | null | 標準の worldName,x,y,z,yaw,pitch 形式のスポーン位置 |
name | string | "Default Name" | NPCの表示名 |
difficulty | string | "Difficulty: Not Set" | NPCの上に表示される難易度ラベル |
disguise | string | null | LibsDisguises記述子(例: custom:etd_tutorial_npc) |
customDisguiseData | string | null | LibsDisguisesの追加コマンドデータ — 通常は長いプレイヤースキン文字列 |
村人は無敵、AI無効、永続化、そしてEternalTDのNPC名前空間キーでタグ付けされた状態でスポーンされます。LibsDisguisesがインストールされており、disguise と customDisguiseData の両方が設定されている場合、村人はスポーン時にディスガイズされます。
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)。有効な場合、プラグインとコンテンツのアップデートが起動時に自動的にダウンロードされます。
manageSpawn が true でスポーンワールドがロードされている場合、サーバーに参加するすべてのプレイヤーは spawnLocations にテレポートされます。EternalTDの初期化中にすでに接続していたプレイヤーも、初期化が完了した時点で、すでにハブワールドにいない限り同じスポーンへ集められます。