跳到主要内容

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关卡中每一个可行走的网格方格,注册地板选择时自动生成
wavesConfigFilestring必填链接的波次配置的文件名(waves/<name>.yml
waveCountint-1缓存的波次计数,当前仅供参考
highscoreWaveint0该关卡达到的最佳波次
highscorePlayerNamestring"no one"创下分数的玩家显示名称
environmentenumNORMAL加载克隆世界时使用的世界环境

整个系统使用的网格大小为每个逻辑方格 3 格(代码中的常量 GRID_SIZE)。

位置字符串格式

EternalTD 中的位置以下列形式的逗号分隔字符串进行序列化:

worldName,x,y,z,yaw,pitch

通常无需手动编写 —— /etd selectfloor 加注册命令会为你计算并保存它们。

世界生命周期

当玩家加入关卡时:

  1. EternalTD 检查该玩家是否已有一份正在复制中的副本。在第一份仍在复制时再次执行 /etd join 会被拒绝,并提示 "Your level is already being prepared."
  2. 通过 worldNameplugins/EternalTD/worlds/ 中查找模板文件夹;如果世界文件夹缺失、关联的波次文件加载失败,或者世界名不是安全的文件夹名,则拒绝加入。
  3. 预留下一个空闲的数字后缀(<worldName>_0<worldName>_1……)。预留过程是同步的,并会记住那些已被预留但尚未写入磁盘的名称,因此两名玩家同时加入不会撞上同一个实例名。
  4. 模板会在主线程之外被复制到服务器世界容器中,因此服务器不会因为地图过大而卡顿。
  5. 通过 MagmaCore 的 TemporaryWorldManager 将克隆世界加载为临时空世界,从而隔离 Paper 26.1+ 迁移,并且缺失的区块会以虚空形式返回,而不是重新生成地形。
  6. 玩家被传送到新世界;内部的 InstanceProtector 应用 EternalTD 的保护规则。

模板要求

复制会在写入任何内容之前进行校验,出现以下情况时模板会被拒绝:

  • 它不是目录,或者它是一个符号链接
  • 它的根目录下没有 level.dat
  • 它内部的任何位置包含符号链接
  • 它包含名为 .eternaltd-instance 的文件(该名称是保留的,见下文)

复制失败会被回滚,并告知玩家关卡未能启动。

实例归属标记

每个克隆都会在其根目录写入一个 .eternaltd-instance 文件,记录模板名称和实例名称。EternalTD 只会删除标记与它认为正在删除的实例相匹配的世界文件夹。这道防线可以避免过期的名称冲突、手工创建的文件夹或不匹配的会话删掉并非由 EternalTD 创建的世界 —— 如果标记缺失或不匹配,该文件夹会被原样保留,并改为记录一条警告。

因崩溃而残留的过期实例会在关卡加载时被清理,而且仅限于携带有效标记且当前未被加载的文件夹。

会话启动校验

只有以下条件全部成立,会话才会变得可游玩。任何一项失败都会中止本局、恢复玩家状态(物品栏、游戏模式、飞行、记分板)并删除临时世界:

  • 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 中的出生位置,如果未配置出生位置则会被踢出。
  • 该世界的保护规则会被移除,所有剩余的塔都会被拆除(在移除过程中抛出异常的塔会被记录并跳过,而不会导致其余的塔无法处理)。
  • 克隆世界会被卸载并从磁盘删除(TemporaryWorldManager.permanentlyDeleteWorld),旧版布局和 Paper 26.1+ 迁移后的布局都会处理。
  • 结束操作是幂等的 —— 第二次 end()(例如 /etd quit 之后紧接着 /etd hub)不会产生任何效果。

实例保护规则

在关卡活跃期间,克隆世界会应用以下规则:

  • 禁用爆炸
  • 禁用液体流动
  • 禁用鞘翅
  • 阻止切换飞行
  • 阻止友军伤害
  • 阻止原版生物生成

地图创作工作流程

当前的地图创作流程使用游戏内工具:

  1. plugins/EternalTD/worlds/<worldName>/ 下放置一个模板世界文件夹。
  2. plugins/EternalTD/levels/ 中创建或下载一个匹配的关卡 YAML。
  3. 运行 /etd reload 并手动加入关卡世界(或在单人模式下打开以进行设置)。
  4. 使用 /etd selectfloor,并通过右键/左键点击两个角落来标记游戏区域,或使用 /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* 路径 —— 一条地面路径和一条空中路径。该缓存会在会话开始时、每次放置塔时以及每次出售塔时重建。

Air 敌人使用那条单独的空中路径,该路径完全忽略塔的阻挡,并改为遵循空中偏移量(配置路径上方 4 格)。

该缓存同时也是判断关卡是否可游玩的关口:如果任何起始瓦片缺少地面路径或空中路径,会话会在加入时就被拒绝,而不是启动一局敌人无法走完的对局。放置塔时会运行同样的检查,因此会封死某个起始瓦片的塔会被拒绝,金币不会被消耗。

未位于其 3x3 网格方格中心的起点或终点位置会被丢弃并在控制台记录警告,这通常就是关卡报告路径集不完整的原因。

NPC 与关卡菜单

plugins/EternalTD/npcs/ 中的 NPC 配置将村民 NPC 与一个或多个关卡链接起来。右键点击 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
customDisguiseDatastringnull额外的 LibsDisguises 命令数据 —— 通常是较长的玩家皮肤字符串

村民生成时具有无敌、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 刻被传送到出生位置。
  • playerGuide —— 游戏内指南书的文本。
  • nightbreak.autoDownloadPluginUpdates —— 共享的 MagmaCore 设置(默认 false)。启用时,插件和内容更新会在启动时自动下载。

manageSpawn 为 true 且出生世界已加载时,每个加入服务器的玩家都会被传送到 spawnLocations。在 EternalTD 仍在初始化期间就已连接的玩家,会在初始化完成后被一并扫描并传送到同一出生点,除非他们已经身处大厅世界。