Усі сторінки форматів ↓
Мапи й рельєф

TNT — рельєф мапи

Джерело: coreprime/kbot @ 0ab97bf · Редагувати на GitHub

Файл .tnt — це повна мапа, готова до гри: графіка тайлів, висоти рельєфу, розміщення об'єктів (дерева, скелі, гідроджерела, …), рівень моря й мінімапа 252×252 — усе в одному самодостатньому файлі. Для геометрії немає жодних зовнішніх залежностей: покладіть .tnt у теку гри, і мапа завантажиться. (Супровідний файл .ota містить лише метадані: назву, опис, підказки для ШІ, стартові позиції.)

Мапа Metal Heck, відрендерена зі спрайтами об'єктів і позначками стартових позицій
maps/metal heck.tnt — повний попередній перегляд зі спрайтами об'єктів, скомпонованими з features/*.tdf,
і позначками стартових позицій із відповідного .ota

Вбудована мінімапа    Нормалізована карта висот
Вбудована мінімапа (ліворуч) і нормалізована сітка висот (праворуч).

Tip

Спробуйте самі.

kbot tnt describe  "maps/metal heck.tnt"
kbot tnt minimap   "maps/metal heck.tnt" -t mini.png
kbot tnt heightmap "maps/metal heck.tnt" --normalize -t h.png
kbot tnt image     "maps/metal heck.tnt" -t map.png        # raw tile mosaic
kbot tnt preview   "maps/metal heck.tnt" -t preview.jpg    # + feature sprites + StartPos

# And the reverse: decompose into editable files, edit, repack.
kbot tnt unpack    "maps/metal heck.tnt" -t ./metal_heck
kbot tnt pack      ./metal_heck -t metal_heck.tnt

Див. довідник CLI kbot tnt.

З Go. Скористайтеся пакетом formats/tnt:

import "github.com/coreprime/kbot/formats/tnt"

f, _ := os.Open("maps/metal heck.tnt")
defer f.Close()
m, _ := tnt.LoadFromReader(f)
fmt.Printf("%dx%d tiles, %d unique, %d features\n",
    m.TileW, m.TileH, m.Header.Tiles, m.Header.TileAnims)

Коротко

┌─ Header (64 B) ─────────────────────────────────────────────┐
│ IDVersion (0x2000)  Width  Height (in 16-px attribute cells)│
│ PtrMapData  PtrMapAttr  PtrTileGfx  Tiles  TileAnims        │
│ PtrTileAnim  SeaLevel  PtrMiniMap  MinimapFlags  pad×4      │
└──────────────────────────────┬──────────────────────────────┘
                               │  offsets are absolute
       ┌─────────────────┬─────┴────────┬──────────────────────┐
       ▼                 ▼              ▼                      ▼
┌─ Map data ────┐ ┌─ Attributes ─┐ ┌─ Tile graphics ─┐ ┌─ Tile animations ─┐
│ uint16 grid   │ │ 4 B per 16px │ │ Tiles × 1024 B  │ │ TileAnims × 132 B │
│  (TileW×TileH)│ │   cell:      │ │  (32×32 idx)    │ │ name[128] each    │
│               │ │  h, feat, _  │ │                 │ │                   │
└───────────────┘ └──────────────┘ └─────────────────┘ └───────────────────┘
                                                                ▼
                                              ┌─ Minimap (252×252 paletted) ─┐
                                              └──────────────────────────────┘

Заголовок (64 байти)

typedef struct {
    uint32 IDVersion;       // 0x2000 (TA); 0x1020 is the older TA layout
    uint32 Width;           // In 16-pixel attribute cells (tile width = Width/2)
    uint32 Height;          // In 16-pixel attribute cells
    uint32 PtrMapData;      // → tile-index grid
    uint32 PtrMapAttr;      // → per-cell attributes
    uint32 PtrTileGfx;      // → tile pixel data
    uint32 Tiles;           // unique tile count
    uint32 TileAnims;       // feature table count (must be < 0xFFFB)
    uint32 PtrTileAnim;     // → feature name table
    uint32 SeaLevel;        // heights below this = underwater
    uint32 PtrMiniMap;      // → embedded 252×252 minimap
    uint32 MinimapFlags;    // 0x2c: bit 0 set = the game reads the minimap
    uint32 pad1, pad2, pad3, pad4;  // observed all zero
} TNTHeader;

Note

Існує три слова версії. 0x2000 — це мапа Total Annihilation, яку записує кожен інструмент. 0x1020 — старіша структура TA, яку TA 3.1c досі читає: її записи атрибутів мають 8 байтів (висота, далі однобайтовий об'єкт у байті 2, де 0xFC і більше означає «немає»), а вказівник на мінімапу та прапорці містяться за 0x38/0x3c; kbot її читає, а під час збереження записує 0x2000. 0x4000 — це мапа TA: Kingdoms, яку TA завантажити не може (див. Мапи TA:K); про це повідомляють kbot tnt describe, MCP-інструмент tnt_describe і вікно вибору Open Map у студії.

Important

Width і Height задано в 16-піксельних клітинках атрибутів, а не в тайлах. Поділіть на 2, щоб отримати кількість тайлів; помножте на 16, щоб отримати розміри в пікселях. Тож мапа 4096×4096 px має Width = Height = 256 (клітинок) і 128 × 128 = 16384 тайли в сітці індексів.


Сітка індексів тайлів (PtrMapData)

(Width/2) × (Height/2) елементів uint16 у порядку рядків (Y — зовнішній цикл, X — внутрішній). Кожен елемент — це індекс у масиві графіки тайлів. Тайли мають розмір 32 × 32 px; сітка — це мозаїка, яку гра виводить (blit), щоб намалювати ігрове поле.

Note

Інструменти Cavedog доповнюють цей блок до межі 16 байтів вмістом робочої пам'яті. kbot tnt unpack записує байти доповнення без змін у metadata.json (якщо задано --lossless), тож round-trip дає результат, ідентичний до байта.

Сітка атрибутів (PtrMapAttr)

Width × Height клітинок по 4 байти в порядку рядків. Для кожної клітинки:

typedef struct {
    uint8  Height;   // Elevation (0..255)
    uint16 Feature;  // Feature placement index into the feature table,
                     //   or 0xFFFF if no feature,
                     //   or 0xFFFC if the cell is void / impassable
    uint8  _pad;     // Zero; alignment to 4 bytes
} TNTAttrCell;

Гра розміщує об'єкт лише для слова, меншого за кількість записів у таблиці об'єктів (TileAnims); 0xFFFC позначає порожню (void) клітинку, а будь-яке інше слово нічого не розміщує. Слова від 0xFFFB і вище — сторожові значення незалежно від розміру таблиці, тож таблиця мусить містити менше ніж 0xFFFB записів. kbot tnt describe, kbot tnt features і оглядач ресурсів рахують лише ті розміщення, які робить гра.

Сітка атрибутів удвічі щільніша за сітку тайлів по кожному виміру: кожен тайл має 4 клітинки атрибутів, по одній на кожен 16-піксельний підтайл. Саме це дає рушію змогу визначати шляхи юнітів, місця для будівництва й зіткнення зброї з точністю до 16 пікселів.

Warning

0xFFFE і 0xFFFD трапляються в релізному вмісті (Lava Run, Metal Heck і кілька ранніх мап кампанії). Це не індекси об'єктів і не порожні клітинки: гра нічого там не розміщує, і на клітинках можна будувати. kbot зберігає початкове значення під час round-trip.

Графіка тайлів (PtrTileGfx)

Tiles × 1024 байтів індексів палітри: згори вниз і зліва направо в межах кожного блоку 32×32, блоки збережено підряд. Мапа містить усю графіку своїх тайлів усередині — покладіть TNT у теку гри, і на ній можна грати.

Імена об'єктів (PtrTileAnim)

TileAnims записів такого вигляду:

typedef struct {
    uint32 Index;       // Matches array position; safe to ignore on read
    char   Name[128];   // NUL-padded, references a [FEATURE] section
                        //   in features/*.tdf
} TNTFeatureEntry;

Ці імена розв'язуються в секції [FEATURE] у features/**/*.tdf (див. TDF) — так рушій дізнається, яку 3D-сітку .3do і який спрайт .gaf малювати для «MetalTower10» чи «ArmTreeFoo». Запис TDF об'єкта також містить площу основи, шкоду, вартість переробки тощо.

Note

Інструменти Cavedog лишають неініціалізовані байти робочої пам'яті в кінцевій частині кожного Name[128] після завершального NUL. kbot tnt unpack (за замовчуванням) записує в features.csv лише частину, придатну для друку; round-trip відновлює чисті таблиці. Передайте --lossless, щоб зберегти сирі байти точно й отримати пакування, ідентичне до байта.

Мінімапа (PtrMiniMap)

uint32 Width;   // 252
uint32 Height;  // 252 (33 of the 275 retail maps store 256)
uint8  pixels[Width * Height];  // Palette indices

Гра читає збережену мінімапу лише тоді, коли встановлено біт 0 слова заголовка за 0x2c (MinimapFlags; 0x3c у мапі 0x1020); інакше вона будує зображення радара з тайлів. Усі 275 релізних мап встановлюють цей біт, як і кожна мапа, збережена студією. Радар гри використовує збережену мінімапу, лише якщо обидві її сторони мають щонайменше 252.

Мапу намальовано в лівому верхньому куті мінімапи, а решта — це заповнення індексом палітри 0x64 (кілька мап кампанії заповнюють 0). Ця область показує видиму мапу, яка на 32 пікселі вужча й на 128 пікселів нижча за тайли (ці краї гра ніколи не показує): її довша сторона дорівнює 252, а інша зберігає пропорції видимої мапи з округленням униз:

visible_w = Width  * 16 - 32;    // Width/Height in attribute cells
visible_h = Height * 16 - 128;
if (visible_w >= visible_h) { region_w = 252; region_h = 252 * visible_h / visible_w; }
else                        { region_h = 252; region_w = 252 * visible_w / visible_h; }

Тож 225×196 тайлів дають область 252×216, 128×96 (ac04) — 252×182, а 96×128 — 193×252. Область визначається розміром мапи, а не пікселями: деякі релізні мінімапи взагалі не мають заповнення (ac01, ac04–ac11 заповнюють 252×256), а область усе одно та, що обчислена. Її обчислюють Map.MinimapContentBounds і MinimapContentSize у kbot-io; оглядач ресурсів розміщує в ній позначки StartPos, а студія зберігає мінімапу, побудовану саме так (Map.BuildMinimap: кожен піксель мінімапи — це середній колір пікселів мапи, які він покриває).


Розібраний приклад — metal heck.tnt

$ kbot tnt describe "maps/metal heck.tnt"
File Size: 973176 bytes

Header:
  IDVersion:   0x2000 (Total Annihilation)
  Width:       262 (16px cells) -> 131 tiles, 4192 pixels
  Height:      262 (16px cells) -> 131 tiles, 4192 pixels
  SeaLevel:    1
  Tiles:       583 unique
  Features:    28 in table, 74 placements
  Minimap:     252x252
  MinimapFlags: 1 (bit 0 set, the game reads the stored minimap)
  Pads:        0 0 0 0

Elevation:
  min=13 max=181 mean=24.7  cells below sealevel: 0 (0.00%)

Top features:
  [  6] MetalTower10  count=10
  [ 14] MetalTower09  count=9
  [  5] MetalVent01   count=8
  ...

Як це читати: Metal Heck — мапа 4192×4192 пікселі, складена з 583 унікальних тайлів, із 74 розміщеннями об'єктів, що спираються на 28 різних визначень об'єктів. Визначення .tdf для цих об'єктів надає архів світу «Metal».


Розпакування й редагування

kbot tnt unpack <file.tnt> --target <dir> створює структуру, яку відповідна команда pack читає назад:

metal_heck/
├── map.png            full RGBA render of the tile mosaic
├── heightmap.png      8-bit grayscale, pixel value = raw elevation byte
├── minimap.png        paletted PNG of the embedded minimap
├── tiles/<n>.png      paletted 32×32 per unique tile
├── tilemap.csv        2D grid of tile indices (rows = y, cols = x)
├── features.csv       feature_index,name,attr_x,attr_y per placement
└── metadata.json      header constants + round-trip data

Відредагуйте будь-що з наведеного й запакуйте знову:

kbot tnt pack ./metal_heck --target metal_heck.tnt

Important

Розпакування за замовчуванням іде із втратами. Таблиця імен об'єктів не потрапляє до metadata.json і під час пакування відбудовується з унікальних імен у features.csv. Через це губляться кінцеві байти робочої пам'яті, які інструменти Cavedog лишили в таблиці. Для round-trip, ідентичного до байта, передайте --lossless, щоб записати сирі байти об'єктів:

kbot tnt unpack "metal heck.tnt" -t ./metal_heck --lossless

Супутні файли .ota

Кожен .tnt постачається із супровідним файлом .ota («Online Total Annihilation») у тому самому каталозі. Саме там містяться метадані місії: зрозуміла людині назва, назва планети, погода, гравітація, стартові позиції, вказівки для ШІ тощо.

Див. TDF: .ota — той самий текстовий формат у стилі INI, що й .fbi та .tdf. kbot tnt preview автоматично читає його, щоб намалювати на попередньому перегляді нумеровані позначки стартових позицій. Кожне подання мапи в kbot читає схеми й стартові позиції з .ota так само, як гра (див. TDF — OTA); редактор мап у студії під час збереження редагує .ota відкритої мапи на місці.


Підводні камені

Warning

Редактор мап (TAE) у деяких версіях записує Height = actual_height + 6 — документація Saruman 1997 року зауважувала: «дивно, на 6 більше, ніж використовується». Сучасні інструменти вважають розміри в заголовку визначальними й ігнорують зайві кінцеві рядки. Читач kbot поводиться так само.

  • SeaLevel == 0 НЕ означає «води немає» — це означає, що байт висоти 0 є найвищою підводною висотою, тобто під водою немає нічого. Мапи без води зазвичай використовують SeaLevel = 1.
  • Індекси тайлів — uint16, тож одна мапа може мати до 65 536 унікальних тайлів, але на практиці вже кілька тисяч змусять рушій через брак пам'яті вдатися до свопінгу. Metal Heck (583 тайли) ближча до медіани, ніж до хвоста розподілу. Студія відмовляється зберігати мапу, якій потрібно більше, а kbot tnt pack відхиляє значення в tilemap.csv, які не є індексами тайлів мапи (як-от -1 чи 70000, що перетворилося б на 4464). Клітинка, що посилається на тайл за межами набору тайлів, завантажується (гра читає за межами свого набору тайлів); kbot tnt lint повідомляє про неї як bad-tile-index.
  • Заповнення мінімапи — це індекс палітри 0x64; пікселі поза областю мапи рушій ігнорує.
  • Біт 0 MinimapFlags (0x2c, або 0x3c у мапі 0x1020) має бути встановлено, інакше гра ігнорує збережену мінімапу. Інші біти зберігайте такими, якими вони були.
  • Чотири поля pad завжди нульові. Деякі сторонні редактори записують ненульові значення; гра, схоже, їх ігнорує, але kbot збереже все, що знайде.
  • TA: Kingdoms використовує споріднений, але несумісний формат мап (карти висот .btm / .bmp + сторінки тайлів). Див. Мапи TA:K.

Типові розміри

Ресурс Діапазон, що трапляється в maps/*.tnt від Cavedog
Розмір файлу 0,3 – 7 МБ
Розміри мапи (у тайлах) 64×64 – 256×256 (тобто сторони 2048 – 8192 px)
Унікальні тайли 200 – 2000
Розміщення об'єктів 10 – 600
Визначення об'єктів у таблиці 5 – 50
Накладні витрати на заголовок завжди 64 байти
Мінімапа (252×252, палітрова; 33 релізні мапи — 252×256) 63504 байти
Розмір тексту супровідного .ota 1 – 8 КБ

Живі приклади в довідковому каталозі

Мапи, які варто вивчити, — видобудьте їх із будь-якої інсталяції TA за шляхом maps/<name>.tnt:

  • metal heck.tnt — використано в розібраному прикладі вище. 4192×4192 px, 583 унікальні тайли, 74 розміщення об'єктів. Культова мапа, багата на ресурси.
  • a gentle time.tnt — невелика мапа на 4 гравців; добрий початок, коли робите прототип переглядача TNT або конвеєра рендерингу.
  • lava run.tnt — має незвичні сторожові значення об'єктів 0xFFFE у клітинках атрибутів (одна з мап, на яких виявилися граничні випадки під час раннього реверс-інжинірингу).
  • acid foursome.tnt — велика мапа на 8 гравців, демонструє широко розкидані стартові позиції [Schema 0] у сусідньому .ota.
  • example.tnt — найменший постачуваний TNT (~263 КБ); мінімальна робоча мапа для перевірки round-trip через kbot tnt unpack / pack.

Про різновиди TNT у TA: Kingdoms (з іншим словом IDVersion) див. дослідницькі нотатки про мапи TA:K.


Див. також

  • SCT — будівельні блоки, з яких TAE збирає TNT.
  • TDF — визначення об'єктів, що розв'язуються за іменем, і формат супутнього файлу .ota.
  • PAL — палітра, якою рендеряться пікселі тайлів і мінімапа.
  • GAF — спрайти об'єктів, які компонує kbot tnt preview.
  • Мапи TA:K — зовсім інший конвеєр мап TA: Kingdoms.
  • Глосарій — attribute cell, sea level, void cell.