所有格式页面 ↓
地图与地形

横扫千军:王国的地图——.tnt + .ota + .crt + .kmp

来自 coreprime/kbot @ 0ab97bf · 在 GitHub 上编辑

《横扫千军:王国》的地图是一种 TNT 变体(IDVersion 字不同),外加一到两个元数据文件;单人任务则另用一种 HPI v2 战役压缩包格式。这与 TA 的地图模型远比早先那份“BTM/高度图 BMP”研究笔记所设想的接近——查看实际的 GOG 版 TA:K 安装目录就会发现,里面根本没有 .btm 或附属 BMP 文件。

kbot 现有的 HPI v2 读取器可以透明地处理 .kmp 压缩包。kbot 的 TNT 读取器能识别 TA:K 的版本字,并解码文件头、DataUnit 高度图、纹理映射网格(地形名表 + U/V 偏移表)、地图物件放置网格 + 名称表,以及内嵌的小地图——足以把外部地形 JPG 和地图物件的 GAF 精灵图合成起来,以完整分辨率渲染出整张地图。本页记述能从发行版安装文件中验证的内容,以及尚存的空白。

Important

状态更新(相对于本页最初的“研究笔记”版本)。 本页过去讨论的那些文件类型——.btm、附属 BMP、按阵营区分的调色板 PCX——在发行的 GOG 版横扫千军:王国安装目录中实际上并不存在。真正的文件类型记述如下。若要与另一个开源 TA:K 项目对照,可参阅 iiCompleteDestruction;对于某些 TA:K 专有的结构,它的解析器实现比 kbot 更完整。


组成一张 TA:K 地图的文件

对 GOG 版安装目录中 maps 目录的统计:

扩展名 数量 用途
.kmp 181 HPI v2 压缩包,打包一个战役任务的地图 + 脚本 + 资源
.tnt 55 地图地形数据(TNT 结构的二进制文件,带 TA:K 专有的 IDVersion 字)
.ota 55 地图元数据——TDF 格式,结构与 TA 的 .ota 相同,另有 TAK 专有字段
.txt 55 自由文本的地图描述(寥寥数词,显示在大厅中)
.crt 33 场景附属文件:预置单位、按玩家划分的规则引擎、命名的触发区域
.tdf 6 按地图划分的玩法调整文件(战役调校、AI 引导)

典型的多人游戏地图以 .tnt + .ota + .txt 三件套发布,有时附带一个 .crt 附属文件。战役任务则打包在 .kmp 压缩包里,其中包含同样一组文件和任务脚本。

Tip

不解包,直接列出一个 TA:K 地图包的内容:

# The HPI v2 reader handles .kmp transparently
kbot hpi list /path/to/tak/maps/adamantine\ gate.kmp -v
kbot hpi info /path/to/tak/maps/adamantine\ gate.kmp

.tnt——TA:K 地形(TNT v2)

TA:K 沿用了 TA 的 TNT 结构,但改动了 IDVersion 字。abnar's terrace.tnt 的十六进制转储:

00000000: 00 40 00 00 20 01 00 00 e0 00 00 00 3a 00 00 00   .@.. .......:...
00000010: 34 00 00 00 34 fc 00 00 34 f4 02 00 16 00 00 00   4...4...4.......

第一个 uint32 是 0x00004000——而 TA 的 TNT 用的是 0x00002000。容器仍然是文件头加上一组按指针寻址的区段,但 TA:K 根本不是图块拼接格式。 TA 存储的是图块索引网格 + 一组 32×32 的图块图形,而 TA:K 把地形存储为引用外部 JPG 纹理的纹理映射网格,另加独立的高度图和地图物件放置网格。以下内容均已在全部 55 张发行地图上验证:

  1. Width/Height 是以 16 像素的 DataUnit(数据单元)计的数量,而不是像素数。 像 athri cay 这样的“15 × 15”地图存储的是 Width = Height = 480 个 DataUnit → 每边 480 × 16 = 7680 像素。高度图和地图物件网格按 DataUnit 采样;地形则在更粗的 32 像素 Graphic Unit(图形单元)网格上采样,每个轴上恰好是 DataUnit 网格的一半(guW = Width/2)。
  2. 文件头由十二个连续的 uint32 组成,内容是偏移量和计数;TA 的字段名已不再能描述其内容。下表给出它们在 TA:K 中的含义。
  3. 区段指针是绝对偏移,因此每个区段都按它自己的指针 + 算出的长度读取。
偏移量 TA:K 字段 区段大小 说明
0x00 Version — 0x4000(TA 写的是 0x2000)
0x04 Width — 以 DataUnit 计;×16 → 像素,÷2 → Graphic Unit
0x08 Height — 以 DataUnit 计
0x0C SeaLevel — 水面的高度阈值
0x10 HeightMapOffset Width × Height 字节 每个 DataUnit 一个高度字节
0x14 AttributesOffset Width × Height × 2 地图物件网格:每个 DataUnit 一个 uint16 索引(≥0xFF00 = 无)
0x18 FeatureNamesOffset Count × 132 地图物件名称表(4 字节索引 + 128 字节名称)
0x1C FeatureCount — 地图物件类型的数量
0x20 TerrainNamesOffset guW × guH × 4 每个 GU 的纹理名 → terrain/%08x.jpg
0x24 UMappingOffset guW × guH 字节 每个 GU 在其纹理中的列偏移(×32 像素)
0x28 VMappingOffset guW × guH 字节 每个 GU 在其纹理中的行偏移(×32 像素)
0x2C MiniMapOffset 8 + 126×126 uint32 w、uint32 h,然后是索引

高度图(0x10)每个 DataUnit 一个字节——是一个灰度海拔场,不是彩色位图。地形通过纹理映射重建:对每个 Graphic Unit,地形名表(0x20)给出一个纹理文件(terrain/<name>.jpg,名称格式化为小写的 8 位十六进制数),U/V 表(0x24/0x28)给出要从该 JPG 中 (u×32, v×32) 处复制的 32×32 子图块。地图物件网格(0x14)是放置层:一个 Width × Height 的 uint16 数组,每个格子要么是指向名称表的地图物件索引,要么是一个高位哨兵值(≥0xFF00),表示“此处没有地图物件”;放置位置所在的 DataUnit 格子 (x, y) 对应地形像素 (x×16, y×16)。名称表(0x18)的 132 字节条目布局与 TA 的 TileAnim 表相同。

在全部发行地图中,小地图块一律是 126 × 126,并且与 TA 一样,用同一个 0x64 空白字节填充非正方形地图之外的区域。

Note

目前在 TA:K TNT 上能做到的。 kbot tnt describe 报告文件头、所属王国、高度图/地形尺寸以及地图物件的分类统计。kbot tnt preview --vfs <root> 渲染完整分辨率的纹理映射地形,并把每个地图物件的 GAF 精灵图按其热点锚定合成在上面——也就是游戏所画出的完整地图。kbot tnt image 渲染自包含的灰度高度图(不需要外部纹理;--features 会叠加按类别着色的标记)。kbot tnt minimap 导出内嵌的 126 × 126 预览图。

小地图调色板——每个王国一个

TA:K 不用单一的全局调色板为小地图着色。每张地图的 126 × 126 预览图都是按其所属王国的纹理调色板烘焙的——即同目录 .ota 中的 kingdom= 字段(aramon / taros / veruna / zhon / creon)。用错误王国的调色板(或 TA 的 PALETTE.PAL)渲染同样的索引,会得到满是斑点的噪声;用正确的调色板渲染,则能得到自然的水面、海滩和地形。

kbot tnt minimap 会从同目录的 .ota 自动解析出王国。没有 .ota 时(例如单独的一个 .tnt 散文件),请显式传入 --kingdom <name>:

kbot tnt minimap "athri cay.tnt" -t mini.png          # auto: veruna
kbot tnt minimap loose.tnt --kingdom aramon -t mini.png

.ota——地图元数据(TDF,大体熟悉)

TA:K 的 .ota 与 TA 的一样是 TDF 结构的格式,另有几个 TAK 专有字段。以下是 abnar's terrace.ota 的完整内容:

[GlobalHeader]
{
    Copyright=Copyright 1998 Cavedog Entertainment. All rights reserved.;
    missionname=Abnar's Terrace;
    missiondescription=9 x 7  4 Player  32MB;
    kingdom=veruna;          // ← TA:K-specific: the side the map "belongs" to
    numplayers=4;
    size=9 x 7;              // ← TA:K-specific: free-text size for the lobby
    memory=32 MB;            // ← TA:K-specific: ~RAM requirement for the lobby
    hasscenario=0;
    [Map Data]
    {
        Type=Network 1;
        aiprofile=DEFAULT;
        [specials]
        {
            [special0] { specialwhat=StartPos1; XPos=37;   ZPos=190; }
            [special1] { specialwhat=StartPos2; XPos=215;  ZPos=38;  }
            …
        }
    }
}
字段 TA 中的对应 说明
kingdom= 无 阵营归属(aramon / taros / veruna / zhon / creon)。大厅按它筛选;它还选定了 .tnt 烘焙预览图所用的地形/小地图调色板。
size= 无 给大厅看的、便于阅读的地图尺寸。
memory= 无 建议内存。仅作显示。
hasscenario= 无 战役任务为 1,遭遇战地图为 0。
[Map Data] [Schema N] 把方案子节映射为单一一个块(TA:K 的每张地图只发布一个方案)。

其余一切(StartPos1..N、aiprofile= 等)的用法都与 TA 完全相同——kbot mount 的 OTA 查看器无需修改即可处理两者。


.kmp——战役任务的 HPI v2 压缩包

.kmp 是一个改了扩展名的 HPI v2 压缩包。adamantine gate.kmp 开头的十六进制转储:

00000000: 48 41 50 49 00 00 02 00 57 32 02 00 77 00 00 00   HAPI....W2..w...
00000010: 15 32 02 00 42 00 00 00 20 00 00 00 00 00 00 00   .2..B... .......
00000020: 53 51 53 48 …                                     SQSH…

完全是 v2 的文件头结构(HAPI 标记 → 0x00020000 版本号 → 24 字节的 v2 文件头 → SQSH 块)。kbot hpi list、info、extract 都能直接使用。每个 .kmp 通常打包了:

  • 该任务的 .tnt 和 .ota
  • 少量过场 / 任务简报 GAF
  • 任务脚本 COB 文件——TA:K 沿用 COB 格式(VersionSignature == 6)编写任务脚本,而不是另用一种脚本语言。零售版 TAK 安装中 3,885 处 MISSION_COMMAND(0x10073000)操作码都在这里。其线上格式(wire format),以及任务命令所索引的、每个 COB 各自的音效名字符串池,见 cob.md 中的横扫千军:王国——COB v6 差异和附录 C。
  • 有时还有针对单个任务的单位覆盖

.crt——场景脚本(单位、规则、触发器)

.crt 承载地图的脚本层:开局前就已放置好的单位、驱动遭遇战 AI 和战役目标的按玩家划分的规则引擎,以及这些规则所引用的命名矩形触发区域。多人游戏地图发布的是一个空壳文件(stub):没有单位,九个空的玩家槽,没有触发器,共 56 字节;战役地图和特殊地图则填满每个区段,最大约 120 KB。

整个文件都是小端序,是引擎内存中结构体的直接映像,因此单位记录中有几个未初始化的填充字段。

偏移量 类型 字段 说明
0x00 uint32 Signature 0x3F800000——IEEE-754 1.0f 标记
0x04 uint32 Unknown1 除一张以外的所有发行地图上都是 0
0x08 uint32 UnitCount 随后的预置单位记录数

abnar's terrace.crt(一个空壳文件)的开头:

00000000: 00 00 80 3f 00 00 00 00 00 00 00 00 09 00 00 00

1.0f、Unknown1 = 0、UnitCount = 0,然后是玩家数 9。

单位记录(每条 568 字节)

偏移量 类型 字段
+0 char[256] UnitType(以 NUL 结尾)
+256 char[256] UniqueName(通常为空)
+512 uint32 X(世界坐标)
+516 uint32 Y(高度轴)
+520 uint32 Z(世界坐标)
+524 uint32 PlayerId(从 0 开始)
+528 uint32 HealthPercent(通常为 100)
+532 uint32 ArmorPercent(通常为 100)
+536 uint32 WeaponPercent(通常为 100)
+540 uint32 Angle(通常为 180)
+544 uint32 Veteran
+548 uint32 未知
+552 uint32 未知
+556 uint32 FootprintX
+560 uint32 FootprintZ
+564 uint32 未知

玩家、规则、条件、动作

单位之后是一个 uint32 玩家数(总是 9),其后跟着同样数量的玩家块。每个玩家块是一个 uint32 规则数,后面跟着同样数量的规则。一条规则的结构是:

  • uint32 条件数,随后每个条件是一个 uint32 操作码加五个 64 字节的参数槽;
  • uint32 动作数,随后每个动作是一个 uint32 操作码加五个 64 字节的参数槽。

触发器

文件末尾的一个 uint32 触发器数之后,跟着同样数量的触发器记录。每条记录是一个 256 字节、以 NUL 结尾的名称,后跟四个 uint32 边界——Left、Top、Right、Bottom——以地图格子为单位。名称是便于阅读的文字(The Hill、Player 1)。

工具

kbot crt describe <map.crt> 打印单位类型的分类统计、规则数和触发区域;加上 --verbose 可列出完整的放置清单。MCP 的 crt_describe 工具以 JSON 形式返回同样的数据。

有一个文件——cairbray coast landing.crt——的文件头经过手工编辑、发生了错位,与这个布局不符;解析器会以明确的错误拒绝它,而不是错误地解码。


.txt——自由文本描述

单行 ASCII 字符串,用于大厅。例如 abnar's terrace.txt:9 x 7 4 Player 32MB(与 .ota 的 missiondescription 中的字符串相同)。制作工具大概是在导出时把两者一起写出的;游戏显示的似乎是 .ota 中的值。


目前如何处理 TA:K 地图

可行的做法,按工作量从小到大排列:

  1. 随意查看——对 .kmp 文件使用 kbot hpi list/info/extract 开箱即用。.ota 和 .txt 是纯文本。
  2. 描述一张地图——kbot tnt describe <map.tnt> 报告 TA:K 文件头、所属王国、高度图/地形尺寸以及地图物件的分类统计。
  3. 渲染小地图——kbot tnt minimap <map.tnt> -t mini.png 以真彩色导出内嵌的 126 × 126 预览图,并根据同目录的 .ota 自动选择王国调色板(可用 --kingdom 覆盖;见上文关于调色板的说明)。
  4. 渲染带纹理的完整地图——kbot tnt preview <map.tnt> --vfs <flattened-tak-root> -t map.png 用安装目录中的 terrain/*.jpg 对地形做纹理映射,并合成每个地图物件的 GAF 精灵图,得到与游戏渲染一致的地图(athri cay → 7680 × 7680)。地图物件精灵图的调色板读自 palettes/<kingdom>_features.pcx。
  5. 渲染高度图——kbot tnt image <map.tnt> -t height.png 不需要任何外部资源;它输出灰度的 DataUnit 高度图(--features 会叠加按类别着色的放置标记)。
  6. 重新打包战役压缩包——kbot hpi pack 目前只能生成 v1,所以重新打包的 .kmp 会无法加载。在 kbot 支持写出 v2 之前,要手工修改 .kmp,只能先解包、修改散文件,再用支持 v2 的第三方打包工具打包。

Warning

横扫千军:王国需要 -disablecavedogverification 才能加载大多数修改过的文件,包括修改过的地图。没有它,引擎会拒绝任何哈希值不在其允许列表中的文件。请在所有模组的安装说明中写明这一点。


kbot 目前对 TA:K 地图能做什么、不能做什么

操作 状态
读取 .kmp(HPI v2)文件头 + 文件列表 ✅
从 .kmp 中提取文件 ✅
解析 .ota(TDF 格式) ✅
读取 TA:K .tnt 文件头(kbot tnt describe) ✅
导出 TA:K .tnt 小地图(kbot tnt minimap) ✅(真彩色——按王国的调色板取自 .ota)
渲染带纹理的完整 TA:K 地图(kbot tnt preview --vfs) ✅(纹理映射地形 + 地图物件 GAF 精灵图)
渲染 TA:K 高度图(kbot tnt image) ✅(自包含的灰度图;--features 叠加标记)
写出 TA:K .tnt(kbot tnt pack) ❌
写出 .kmp(HPI v2 打包) ❌

欢迎针对任何 ❌ 项提交拉取请求。高度图、纹理映射网格、地图物件网格和地图物件表都已解码,地形和地图物件都能以完整分辨率渲染;剩下的空白是让修改过的 TA:K 地图能够往返的写出路径,以及 v2 .kmp 的打包。


另见

  • HPI——v2 压缩包容器(.kmp 就是改了扩展名的 HPI v2)。
  • TNT——TA 的 TNT 格式;TA:K 沿用其结构,IDVersion 为 0x4000。
  • TDF——.ota 所遵循的解析格式。
  • GAF——TA:K 中随处可见的 .taf 动画文件(.kmp 压缩包里的过场动画)。
  • 横扫千军:王国界面——菜单/HUD 控件格式。
  • 术语表——阵营(side)、高度图。