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
下載的內容套件通常會提供這些值。對於自製地圖,目前的選取與註冊指令並不會儲存 levelLocations,因此你必須手動把產生的格線位置寫入關卡 YAML。
世界生命週期
當玩家加入關卡時:
- EternalTD 會檢查這位玩家是否已經有一份複製正在進行中。若第一份仍在複製時再次執行
/etd join,會被以「Your level is already being prepared.」拒絕。 - 它會根據
worldName在plugins/EternalTD/worlds/中查詢範本資料夾;若世界資料夾不存在、連結的波次檔案載入失敗,或世界名稱不是安全的資料夾名稱,就會拒絕這次加入。 - 它會保留下一個可用的數字後綴(
<worldName>_0、<worldName>_1、...)。保留動作是同步化的,並會記住已保留但尚未寫入磁碟的名稱,因此兩位玩家同時加入時不會撞到同一個實例名稱。 - 範本會在主執行緒之外被複製到伺服器的世界容器中,因此伺服器不會因為大型地圖而卡住。
- 複製的世界透過 MagmaCore 的
TemporaryWorldManager作為臨時 void 世界載入,因此 Paper 26.1+ 遷移被隔離,缺失的區塊會回傳為虛空而非新產生的地形。 - 玩家被傳送到新世界;內部的
InstanceProtector套用 EternalTD 的保護規則。
範本需求
複製動作會在寫入任何東西之前先經過驗證,若範本符合下列任一情況就會被拒絕:
- 它不是一個目錄,或者它是一個符號連結
- 它的根目錄沒有
level.dat - 它內部任何位置含有符號連結
- 它含有名為
.eternaltd-instance的檔案(該名稱為保留名稱,見下文)
複製失敗會被回滾,並告知玩家關卡未能開始。
實例歸屬標記
每個複製副本的根目錄都會寫入一個 .eternaltd-instance 檔案,記錄範本名稱與實例名稱。EternalTD 只會刪除標記與它所認定實例相符的世界資料夾。這道防護可避免過時的名稱衝突、手動建立的資料夾,或不相符的工作階段刪掉不是 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!」
當工作階段結束時:
- 複製世界中的任何剩餘玩家都會被傳送回
config.yml中的重生點位置,或者如果沒有設定重生點則被踢出。 - 世界的保護規則會被移除,所有殘留的塔會被拆除(拆除時拋出例外的塔會被記錄並略過,而不會讓其餘的塔卡住)。
- 為遊玩區域取得的區塊票證會被釋放(請參閱路徑驗證)。
- 複製的世界會從磁碟卸載並刪除(
TemporaryWorldManager.permanentlyDeleteWorld),舊有版面配置與 Paper 26.1+ 遷移後的版面配置皆會處理。 - 結束是冪等的——第二次
end()(例如/etd quit之後立刻/etd hub)不會有任何作用。
實例保護規則
當關卡活躍時,複製的世界會套用這些規則:
- 停用爆炸
- 停用液體流動
- 停用鞘翅
- 防止切換飛行
- 防止友軍傷害
- 防止原版怪物生成
地圖創作工作流程
目前的地圖創作流程使用遊戲內工具:
- 將範本世界資料夾放在
plugins/EternalTD/worlds/<worldName>/下。 - 在
plugins/EternalTD/levels/中建立或下載相符的關卡 YAML。 - 執行
/etd reload並手動加入關卡世界(或在單人模式中開啟以進行設定)。 - 使用
/etd selectfloor並右鍵/左鍵點擊兩個角來標記遊戲區域,或使用/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* 路徑——一條地面路徑與一條空中路徑。當有多個終點格子時,它會比較已完成路徑的成本並保留最短的那條,因此一張有多個出口的地圖會把敵人導向最近的可達出口,而不是最先找到的那個。
地面快取會在工作階段開始時、每次放置塔時,以及每次販售塔時重建。空中快取只會計算一次,就在第一次重建時:空中路徑完全忽略塔,因此玩家蓋的任何東西都無法使其失效。
空中敵人使用另外的空中路徑,該路徑完全忽略塔的阻擋,改為沿著空中偏移(設定路徑上方 4 格)行進。
載入遊玩區域時也會強制載入每一個含有格子方塊的區塊,因此長時間的遊玩不會卡在未載入的區塊上。這些區塊票證會在工作階段結束時、實例世界被刪除之前釋放。
該快取同時也是判斷關卡是否可玩的關卡:若任何起點格子缺少地面路徑或空中路徑,該工作階段會在加入時就被拒絕,而不會開始一場敵人無法完成的遊玩。放置塔時也會執行同樣的檢查,因此會封死某個起點格子的塔會被拒絕且不會花費金幣。
起點或終點位置若未位於其 3x3 格線方格的中心,會被捨棄並在主控台發出警告,這通常就是關卡回報路徑集不完整的原因。
每個解析出來的起點與終點格子也都會被標記為不可建造,因此玩家無法藉由在其上建造來封死生成點或出口。這些格子在放置高亮下會顯示為淡藍色。
NPC 和關卡選單
plugins/EternalTD/npcs/ 中的 NPC 設定將村民 NPC 連結到一個或多個關卡。右鍵點擊 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 刻被傳送到重生點位置。playerGuide— 遊戲內指南書文字。nightbreak.autoDownloadPluginUpdates— 共享的 MagmaCore 設定(預設false)。啟用時,外掛和內容更新會在啟動時自動下載。
當 manageSpawn 為 true 且重生世界已載入時,每個加入伺服器的玩家都會被傳送到 spawnLocations。在 EternalTD 仍在初始化期間就已連線的玩家,會在初始化完成後一併被送往同一個重生點,除非他們已經身處大廳世界。