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

EternalTDのレベルとマップ

EternalTDにおける「レベル」とは、plugins/EternalTD/levels/ 内のYAML設定と plugins/EternalTD/worlds/ 内のテンプレートワールドフォルダのペアです。プレイヤーが参加すると、EternalTDはテンプレートワールドをサーバーのワールドコンテナにクローンし、そのクローンされたコピー内でセッションを実行します。

レベル設定フィールド

フィールドデフォルト注記
isEnabledbooltrue無効化されたレベルはロード中にスキップされます
levelNamestringnullメッセージ、スコアボード、NPCメニューに表示される表示名
levelDescriptionstring list[]NPCメニューに表示される行。$highscoreWave$highscorePlayer のプレースホルダをサポート
worldNamestringnullplugins/EternalTD/worlds/ 配下のテンプレートフォルダ名
startLocationstring listnullモブがスポーンするシリアライズされた位置のリスト
endLocationstring listnullモブが向かう先(「赤い」タイル)のシリアライズされた位置のリスト
levelLocationsstring listnullレベル内のすべての歩行可能なグリッドマス。現在のビルドでは、これらはパッケージから供給されるか、YAMLに手動で書き込む必要があります
wavesConfigFilestring必須リンクされたウェーブ設定(waves/<name>.yml)のファイル名
highscoreWaveint0このレベルで到達した最高ウェーブ
highscorePlayerNamestring"no one"スコアを記録したプレイヤーの表示名
environmentenumNORMALクローンされたワールドがロードされる際に使用されるワールド環境

全体を通じて使用されるグリッドサイズは、論理マスあたり 3 ブロックです(コード内の定数 GRID_SIZE)。

waveCount はもう存在しません。このキーをまだ持っている古いレベルファイルは単に無視されます — プラグインはこれを読むことも書き戻すこともありません。

位置文字列の形式

EternalTDの位置は、以下の形式のカンマ区切り文字列としてシリアライズされます:

worldName,x,y,z,yaw,pitch

通常はダウンロードされたパッケージがこれらの値を提供します。カスタムマップの場合、現在の選択コマンドと register コマンドは levelLocations を保存しないため、生成されたグリッド位置をレベルYAMLに手動で書き込む必要があります。

ワールドのライフサイクル

プレイヤーがレベルに参加するとき:

  1. EternalTDは、このプレイヤーのコピーがすでに進行中でないことを確認します。最初のコピーがまだ進行中に2回目の /etd join を実行すると、「Your level is already being prepared.」で拒否されます。
  2. plugins/EternalTD/worlds/ 内で worldName に基づいてテンプレートフォルダを検索し、ワールドフォルダが存在しない、リンクされたウェーブファイルの読み込みに失敗した、またはワールド名が安全なフォルダ名でない場合は参加を拒否します。
  3. 次の空き数値サフィックス(<worldName>_0<worldName>_1、...)を予約します。予約は同期化されており、予約済みだがまだディスク上に存在しない名前も記憶するため、2人のプレイヤーが同時に参加しても同じインスタンス名で衝突することはありません。
  4. テンプレートはメインスレッド外でサーバーのワールドコンテナへコピーされるため、大きなマップでもサーバーが停止することはありません。
  5. クローンされたワールドはMagmaCoreの TemporaryWorldManager を介して一時的なvoidワールドとしてロードされるため、Paper 26.1+ の移行は隔離され、欠けているチャンクは新しく生成された地形ではなくvoidとして戻ってきます。
  6. プレイヤーは新しいワールドにテレポートされます。内部の InstanceProtector がEternalTDの保護ルールを適用します。

テンプレートの要件

コピーは何かが書き込まれる前に検証され、次の場合はテンプレートが拒否されます:

  • ディレクトリでない、またはシンボリックリンクである
  • ルートに level.dat がない
  • 内部のどこかにシンボリックリンクを含んでいる
  • .eternaltd-instance という名前のファイルを含んでいる(この名前は予約されています。後述参照)

コピーが失敗した場合はロールバックされ、レベルを開始できなかった旨がプレイヤーに伝えられます。

インスタンス所有権マーカー

すべてのクローンには、テンプレート名とインスタンス名を記録した .eternaltd-instance ファイルがルートに書き込まれます。EternalTDは、削除しようとしているインスタンスとマーカーが一致するワールドフォルダしか削除しません。これは、古い名前の衝突、手作りのフォルダ、不一致のセッションが、EternalTDの作成していないワールドを消してしまうのを防ぐガードです — マーカーがない、または一致しない場合、フォルダはそのまま残され、代わりに警告がログに出力されます。

クラッシュで残った古いインスタンスはレベルの読み込み時に整理されますが、その場合でも有効なマーカーを持ち、現在読み込まれていないフォルダに限られます。

セッション開始時の検証

セッションがプレイ可能になるのは、次のすべてが満たされた場合だけです。1つでも失敗すると実行は中止され、プレイヤーの状態(インベントリ、ゲームモード、飛行、スコアボード)が復元され、一時ワールドが削除されます:

  • levelLocationsstartLocationendLocation がすべて存在し、空でないこと — そうでなければ「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)は何もしません。

インスタンス保護ルール

レベルがアクティブな間、クローンされたワールドには以下のルールが適用されます:

  • 爆発を無効化
  • 液体の流れを無効化
  • エリトラを無効化
  • 飛行切り替えを防止
  • フレンドリーファイアを防止
  • バニラのモブスポーンを防止

マップ作成ワークフロー

現在のマップ作成フローはゲーム内ツールを使用します:

  1. テンプレートワールドフォルダを plugins/EternalTD/worlds/<worldName>/ 配下に配置します。
  2. 対応するレベルYAMLを plugins/EternalTD/levels/ に作成またはダウンロードします。
  3. /etd reload を実行し、レベルワールドに手動で参加します(またはシングルプレイヤーで開いてセットアップします)。
  4. /etd selectfloor を使用し、右クリック/左クリックで2つのコーナーをマークしてプレイエリアを指定するか、/etd selectfloorcoordinates <x1> <y1> <z1> <x2> <y2> <z2> を使用して直接指定します。
  5. /etd showselection <level> を実行して、選択範囲が正しく見えるか確認します。
  6. /etd register <level> を実行して選択をクリアします。なお、現在のビルドでは registershowselection も実際にはフロア領域を永続化しません — levelLocations を保存するヘルパー(LevelsConfigFields#addLevelLocations)は定義されていますが、どのコマンドからも呼び出されません。ダウンロード済みパッケージで既に設定されていない場合は、現在 levelLocations をレベルYAMLに手動で書き込む必要があります。
  7. スタートスポーンタイルの上に立って /etd register <level> start を実行します。すべてのスタートタイルでこれを繰り返します(このコマンドは startLocation に永続化されます)。
  8. エンドタイルの上に立って /etd register <level> end を実行します。すべてのエンドタイルでこれを繰り返します(このコマンドは endLocation に永続化されます)。
  9. 再度リロードし、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スロットのインベントリが開き、レベル名と説明がラベルとして付きます。

フィールドデフォルト注記
isEnabledbooltrue無効化されたNPCはスキップされます
levelIDsstring list必須このNPCが提供するレベルのファイル名
locationstringnull標準の worldName,x,y,z,yaw,pitch 形式のスポーン位置
namestring"Default Name"NPCの表示名
difficultystring"Difficulty: Not Set"NPCの上に表示される難易度ラベル
disguisestringnullLibsDisguises記述子(例: custom:etd_tutorial_npc
customDisguiseDatastringnullLibsDisguisesの追加コマンドデータ — 通常は長いプレイヤースキン文字列

村人は無敵、AI無効、永続化、そしてEternalTDのNPC名前空間キーでタグ付けされた状態でスポーンされます。LibsDisguisesがインストールされており、disguisecustomDisguiseData の両方が設定されている場合、村人はスポーン時にディスガイズされます。

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の初期化中にすでに接続していたプレイヤーも、初期化が完了した時点で、すでにハブワールドにいない限り同じスポーンへ集められます。