跳到主要内容

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关卡中所有可行走的网格方格。内容包会提供它;自定义地图可以用 /etd register <level> 根据地板选区写入,也可以手动填写
wavesConfigFilestring必填链接的波次配置的文件名(waves/<name>.yml)
highscoreWaveint0该关卡达到的最佳波次
highscorePlayerNamestring"No one"创下分数的玩家显示名称
environmentenumNORMAL加载克隆世界时使用的世界环境

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

waveCount 不再使用。旧文件包含此键也不会决定波次数量;EternalTD 使用关联的波次配置。

位置字符串格式​

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

worldName,x,y,z,pitch,yaw

也支持四段式 worldName,x,y,z(pitch 和 yaw 默认为零),以及不含世界的五段式 x,y,z,pitch,yaw。会话的网格、起点和终点会将指定世界替换为实例世界。大厅和 NPC 位置则需要指定的世界已加载。

下载的内容包通常包含这些值。对于自定义地图,levelLocations 为每个可玩3×3地板方格保存一个中心地板方块的整数坐标,间隔三格。加载器将 X 和 Z 各加0.5,得到世界中的中心位置。/etd register <level> 会根据地板选区写入此列表(参见地图创作工作流程);你也可以手动填写。

世界生命周期​

当玩家加入关卡时:

  1. 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 分钟内世界仍未就绪,都会取消请求并删除复制了一半的副本。
  3. 预留下一个空闲的数字后缀(<worldName>_0、<worldName>_1……)。预留过程是同步的,并会记住那些已被预留但尚未写入磁盘的名称,因此两名玩家同时加入不会撞上同一个实例名。
  4. 模板在主线程之外复制到世界容器。世界加载和会话初始化仍在主线程运行。
  5. 通过 MagmaCore 的 TemporaryWorldManager 将克隆世界加载为临时空世界,从而隔离 Paper 26.1+ 迁移,并且缺失的区块会以虚空形式返回,而不是重新生成地形。
  6. 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 秒后异步执行;清理前重新加载的世界也会被跳过,删除不完整会记录日志。
  • 结束操作是幂等的 —— 第二次 end()(例如 /etd quit 之后紧接着 /etd hub)不会产生任何效果。

实例保护规则​

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

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

对战系统也会守护该世界:本局中的玩家只能通过 EternalTD 自身的移动进出该世界或在其中移动,其他没有 eternaltd.match.bypass 的玩家则无法传送进来。参见对战规则。

地图创作工作流程​

把模板作为可编辑的原始世界,并将网格写入关卡 YAML:

  1. 在单独的世界或单人模式中编辑地图并保存,然后将模板放入 plugins/EternalTD/worlds/<worldName>/。修改对局副本不会更新模板。
  2. 创建关卡 YAML,关联已加载的波次配置,并填写 levelLocations、startLocation 和 endLocation。网格使用地板中心方块坐标,起点和终点使用能对应这些方格的坐标。
  3. 若在编辑世界使用游戏内工具,先运行 /etd reload 加载配置,再运行 /etd selectfloor。左键设置角点1,右键设置角点2。两个角点必须在同一世界、同一地板Y高度,包含两端的X/Z长度必须是3的倍数。
  4. 也可以用 /etd selectfloorcoordinates <x1> <y1> <z1> <x2> <y2> <z2> 在当前世界设置角点。每个输入的Y都会减1,因此请提供地板Y加一。
  5. /etd showselection <level> 会在选区将要注册的每个地板格上方显示一个仅你可见的 END_ROD 粒子。它不保存任何内容。
  6. 执行 /etd register <level> start 或 end 时保持选区有效。这些命令追加玩家的整数方块坐标并立即保存,不会将Y减1,也不检查是否位于网格上。使用前请初始化两个列表,也可以直接在 YAML 中编辑。
  7. 完成这些注册后,运行 /etd register <level>。它会根据选区写入 levelLocations(替换已有列表),保存关卡文件并清除选区。关闭选择模式也会丢弃选区。
  8. 保存编辑后的模板,重新加载并通过 NPC 菜单或 /etd join <level> 加入。等到最终的世界就绪消息后,再实际游玩检查地图。

选择检查器用以下公式计算尺寸,并截断为整数:

size = (abs(corner1 - corner2) + 1) / 3

角点顺序不影响结果。检查器在玩家当前世界中、角点1的Y高度上,从较小的X/Z角加一处开始,每三格检查一次中心方块。中心地板方块可穿过,或其正上方不可穿过时,会跳过该方格。它不会检查3×3内的每个方块。

路径验证​

EternalTD 为每个起点缓存通往某个终点的最低成本 A* 路径,分别保存地面和空中路径。有多个终点时,会比较完整路径的成本并保留最短的路径,使敌人前往最近的可达出口,而非第一个找到的出口。

地面缓存会在会话开始、放置塔和出售塔时重建。空中缓存仅在首次重建时计算一次;空中路径完全忽略塔,因此玩家建造不会使其失效。

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

加载游戏区域时还会强制加载包含网格方格的所有区块,避免长时间对局因区块卸载而停滞。这些票据在会话结束、清理实例世界前释放。

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

起点和终点通过X/Z查找网格方格,不要求精确位于中心,也不比较Y。找不到对应方格的条目会被跳过并警告。虽然警告提示中心不匹配,也请检查目标方格是否存在于 levelLocations。

所有成功解析的起点和终点都会标为不可建造,防止玩家直接在入口或出口建塔封堵。这些方格在放置高亮中呈浅蓝色。

NPC 与关卡菜单​

plugins/EternalTD/npcs/ 中的配置将村民与关卡关联。右键打开9槽菜单,条目位于0、2、4、6、8。只考虑前五个配置的关卡ID;缺失ID留下空位,后续ID会被跳过并警告。没有任何已加载引用关卡的NPC会被完全跳过。左键点击绿色染色玻璃板可请求对应关卡。

字段类型默认值备注
isEnabledbooltrue已禁用的 NPC 会被跳过
levelIDsstring list必填此 NPC 提供的关卡文件名
locationstringnull标准 worldName,x,y,z,pitch,yaw 格式的生成位置
namestring自定义配置为 nullNPC 显示名称;预制定义可能提供名称
difficultystring自定义配置为 nullNPC 上方标签;请明确设置
disguisestringnullLibsDisguises 描述符(例如 custom:etd_tutorial_npc)
customDisguiseDatastringnull额外的 LibsDisguises 命令数据 —— 通常是较长的玩家皮肤字符串

NPC 会在创建其配置四秒后生成,并要求位置所在的世界已加载。村民无敌、无AI、持久存在,并带有 EternalTD 的 NPC 键。LibsDisguises 启用且两个伪装字段都设置时,EternalTD 会尝试伪装;无效描述符可能使其保持普通村民外观。

带有 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)。启用时,插件和内容更新会在启动时自动下载。
  • nightbreak.applyPluginUpdatesWithoutRestart(共享的 MagmaCore 设置,默认 never;也可设为 when-empty 或 immediately)—— 控制已下载的插件更新何时在不重启的情况下应用。参见设置。

初始化完成后,EternalTD 也会为在线玩家请求该传送,但已在目标世界的玩家除外。此过程同样要求 manageSpawn 和已加载的目标。这些登录及初始化完成路径不检查传送结果,因此其他插件可以阻止玩家到达。