Todas as páginas de formatos ↓
Mapas e terreno

TNT — Terreno de mapa

Fonte: coreprime/kbot @ 0ab97bf · Editar no GitHub

Um ficheiro .tnt é um mapa jogável completo: gráficos dos mosaicos, alturas do terreno, colocação dos elementos (árvores, rochas, nós de hidratação, …), nível do mar e um minimapa de 252×252, tudo num único ficheiro autónomo. A geometria não tem dependências externas: basta pôr um .tnt numa pasta do jogo para ele carregar. (O ficheiro .ota que o acompanha só tem metadados: nome, descrição, indicações para a IA, posições iniciais.)

Mapa Metal Heck renderizado com os sprites dos elementos e as marcas das posições iniciais
maps/metal heck.tnt — pré-visualização completa com os sprites dos elementos compostos a partir de features/*.tdf
e as marcas das posições iniciais do .ota associado

Minimapa incorporado    Mapa de alturas normalizado
Minimapa incorporado (à esquerda) e grelha de elevação normalizada (à direita).

Tip

Experimente por si.

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

Consulte a referência de kbot tnt da linha de comandos.

A partir de Go. Use 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)

Visão geral

┌─ 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) ─┐
                                              └──────────────────────────────┘

Cabeçalho (64 bytes)

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

Existem três palavras de versão. 0x2000 é o mapa do Total Annihilation que todas as ferramentas escrevem. 0x1020 é uma disposição mais antiga do TA que o TA 3.1c ainda lê: os seus registos de atributos têm 8 bytes (a altura e depois um elemento de um byte no byte 2, em que 0xFC ou mais significa nenhum), e o ponteiro e as flags do minimapa ficam em 0x38/0x3c; o kbot lê-a e, ao gravar, escreve 0x2000. 0x4000 é um mapa do TA: Kingdoms, que o TA não consegue carregar (veja Mapas do TA:K); kbot tnt describe, a ferramenta MCP tnt_describe e o seletor Open Map do estúdio indicam-no.

Important

Width e Height são expressos em células de atributos de 16 píxeis, não em mosaicos. Divida por 2 para obter o número de mosaicos; multiplique por 16 para obter as dimensões em píxeis. Assim, um mapa de 4096×4096 px tem Width = Height = 256 (células) e 128 × 128 = 16384 mosaicos na grelha de índices.


Grelha de índices de mosaicos (PtrMapData)

(Width/2) × (Height/2) entradas uint16, por linhas (Y no ciclo exterior, X no interior). Cada entrada é um índice no vetor de gráficos dos mosaicos. Os mosaicos têm 32 × 32 px; a grelha é o padrão de mosaicos que o jogo copia para desenhar a área de jogo.

Note

As ferramentas da Cavedog preenchem este bloco até um limite de 16 bytes com restos de memória. kbot tnt unpack regista os bytes de preenchimento tal e qual em metadata.json (com --lossless), para que a ida e volta seja idêntica byte a byte.

Grelha de atributos (PtrMapAttr)

Width × Height células de 4 bytes cada, por linhas. Por célula:

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;

O jogo só coloca um elemento quando a palavra é inferior ao número de entradas da tabela de elementos (TileAnims); 0xFFFC marca uma célula vazia, e qualquer outra palavra não coloca nada. As palavras a partir de 0xFFFB são sentinelas, seja qual for o tamanho da tabela, por isso uma tabela tem de ter menos de 0xFFFB entradas. kbot tnt describe, kbot tnt features e o explorador de recursos só contam as colocações que o jogo faz.

A grelha de atributos é 2× mais densa do que a grelha de mosaicos em cada dimensão: cada mosaico tem 4 células de atributos, uma por cada submosaico de 16 píxeis. É isto que permite ao motor resolver o cálculo de caminhos das unidades, a colocação das construções e as colisões das armas com uma granularidade de 16 píxeis.

Warning

0xFFFE e 0xFFFD aparecem nos conteúdos da versão comercial (Lava Run, Metal Heck e um par de mapas iniciais da campanha). Não são índices de elementos nem células vazias: o jogo não coloca lá nada e continua a ser possível construir nessas células. O kbot preserva o valor original na ida e volta.

Gráficos dos mosaicos (PtrTileGfx)

Tiles × 1024 bytes de índices da paleta, de cima para baixo e da esquerda para a direita dentro de cada bloco de 32×32, com os blocos guardados de forma contígua. O mapa traz toda a arte dos mosaicos embutida: basta pôr um TNT na pasta do jogo para o jogar.

Nomes dos elementos (PtrTileAnim)

TileAnims entradas de:

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;

Estes nomes resolvem-se nas secções [FEATURE] de features/**/*.tdf (veja TDF): é assim que o motor sabe que malha .3do e que sprite .gaf desenhar para "MetalTower10" ou "ArmTreeFoo". A entrada TDF do elemento também contém a área ocupada, o dano, o valor de reciclagem, etc.

Note

As ferramentas da Cavedog deixam bytes de lixo não inicializados na parte final de cada Name[128], depois do terminador NUL. kbot tnt unpack (por omissão) escreve apenas a parte imprimível em features.csv; a ida e volta reconstrói tabelas limpas. Passe --lossless para preservar exatamente os bytes em bruto e obter um empacotamento idêntico byte a byte.

Minimapa (PtrMiniMap)

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

O jogo só lê o minimapa guardado quando o bit 0 da palavra do cabeçalho em 0x2c (MinimapFlags; 0x3c num mapa 0x1020) está definido; caso contrário, constrói a imagem do radar a partir dos mosaicos. Os 275 mapas da versão comercial definem-no, tal como todos os mapas que o estúdio grava. O radar do jogo só usa um minimapa guardado quando os seus dois lados medem pelo menos 252.

O mapa é desenhado no canto superior esquerdo do minimapa, e o resto é preenchimento, com o índice da paleta 0x64 (alguns mapas da campanha preenchem com 0). A região mostra o mapa visível, que é 32 píxeis mais estreito e 128 píxeis mais baixo do que os mosaicos (o jogo nunca mostra essas margens): o lado maior mede 252 e o outro mantém a proporção do mapa visível, arredondado para baixo:

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; }

Assim, 225×196 mosaicos dão uma região de 252×216, 128×96 (ac04) dá 252×182 e 96×128 dá 193×252. A região é fixada pelo tamanho do mapa, e não pelos píxeis: alguns minimapas da versão comercial não têm preenchimento nenhum (ac01 e ac04 a ac11 preenchem 252×256), e a região continua a ser a calculada. Map.MinimapContentBounds e MinimapContentSize do kbot-io calculam-na; o explorador de recursos coloca nela as marcas StartPos, e o estúdio grava um minimapa construído desta forma (Map.BuildMinimap: cada píxel do minimapa é a cor média dos píxeis do mapa que cobre).


Exemplo comentado — 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
  ...

Como ler isto: o Metal Heck é um mapa de 4192×4192 píxeis feito com 583 mosaicos únicos, com 74 colocações de elementos tiradas de 28 definições de elementos distintas. O arquivo do mundo "Metal" fornece as definições .tdf desses elementos.


Desempacotar e editar

kbot tnt unpack <file.tnt> --target <dir> produz uma estrutura que o comando pack correspondente volta a ler:

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

Edite qualquer um destes ficheiros e volte a empacotar:

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

Important

Por omissão, o desempacotamento perde informação. A tabela de nomes de elementos é retirada de metadata.json e reconstruída no empacotamento a partir dos nomes únicos de features.csv. Perdem-se assim os bytes de lixo que as ferramentas da Cavedog deixaram no fim da tabela. Para idas e voltas idênticas byte a byte, passe --lossless, que regista os bytes em bruto dos elementos:

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

Os ficheiros .ota associados

Cada .tnt vem com um ficheiro auxiliar .ota ("Online Total Annihilation") na mesma pasta. É aí que ficam os metadados da missão: o nome legível, o nome do planeta, o clima, a gravidade, as posições iniciais, o briefing para a IA, etc.

Veja TDF: o .ota usa o mesmo formato de texto ao estilo INI que o .fbi e o .tdf. kbot tnt preview lê-o automaticamente para desenhar as marcas numeradas das posições iniciais na imagem de pré-visualização. Todas as vistas de mapas do kbot leem os esquemas e as posições iniciais do .ota tal como o jogo (veja TDF — OTA); ao gravar, o editor de mapas do estúdio altera diretamente o .ota do mapa aberto.


Armadilhas

Warning

O editor de mapas (TAE) escreve Height = actual_height + 6 em algumas versões; a documentação de 1997 de Saruman notava: "estranho, mais 6 do que os usados". As ferramentas modernas consideram determinantes as dimensões do cabeçalho e ignoram as linhas finais não usadas. O leitor do kbot segue este comportamento.

  • SeaLevel == 0 NÃO significa "sem água": significa que o byte de altura 0 é a altura submersa mais alta, ou seja, nada fica submerso. Os mapas sem água usam normalmente SeaLevel = 1.
  • Os índices de mosaicos são uint16, por isso um único mapa pode ter até 65 536 mosaicos únicos; mas, na prática, qualquer coisa acima de alguns milhares obriga o motor a recorrer à memória virtual por falta de memória. O Metal Heck (583 mosaicos) está mais perto da mediana do que da cauda. O estúdio recusa-se a gravar um mapa que precise de mais, e kbot tnt pack recusa valores de tilemap.csv que não sejam índices dos mosaicos do mapa (como -1 ou 70000, que dariam a volta para 4464). Uma célula que indique um mosaico para lá do conjunto de mosaicos carrega na mesma (o jogo lê para lá do seu conjunto de mosaicos); kbot tnt lint assinala-a como bad-tile-index.
  • O preenchimento do minimapa é o índice da paleta 0x64; o motor ignora os píxeis fora da região do mapa.
  • O bit 0 de MinimapFlags (0x2c, ou 0x3c num mapa 0x1020) tem de estar definido, ou o jogo ignora o minimapa guardado. Mantenha os outros bits como os encontrou.
  • Os quatro campos pad são sempre zero. Alguns editores de terceiros escrevem valores diferentes de zero; o jogo parece ignorá-los, mas o kbot preserva o que encontrar.
  • O TA: Kingdoms usa um formato de mapa aparentado, mas incompatível (mapas de alturas .btm / .bmp + páginas de mosaicos). Veja Mapas do TA:K.

Tamanhos típicos

Recurso Intervalo observado em maps/*.tnt da Cavedog
Tamanho do ficheiro 0.3 – 7 MB
Dimensões do mapa (mosaicos) 64×64 – 256×256 (ou seja, lados de 2048 – 8192 px)
Mosaicos únicos 200 – 2000
Colocações de elementos 10 – 600
Definições de elementos na tabela 5 – 50
Espaço ocupado pelo cabeçalho sempre 64 bytes
Minimapa (252×252 com paleta; 33 mapas da versão comercial com 252×256) 63504 bytes
Tamanho do texto do .ota associado 1 – 8 KB

Exemplos reais no catálogo de referência

Mapas notáveis para estudar; extraia-os de qualquer instalação do TA, em maps/<name>.tnt:

  • metal heck.tnt — usado no exemplo comentado acima. 4192×4192 px, 583 mosaicos únicos, 74 colocações de elementos. Um mapa icónico, rico em recursos.
  • a gentle time.tnt — mapa pequeno para 4 jogadores; um bom ponto de partida para o protótipo de um visualizador de TNT ou de um processo de renderização.
  • lava run.tnt — tem sentinelas de elementos 0xFFFE invulgares nas células de atributos (um dos mapas que deram origem aos casos-limite dos primeiros trabalhos de engenharia inversa).
  • acid foursome.tnt — mapa grande para 8 jogadores; mostra posições iniciais [Schema 0] muito afastadas umas das outras no .ota associado.
  • example.tnt — o TNT mais pequeno incluído no jogo (~263 KB); o mapa mínimo viável para testar a ida e volta de kbot tnt unpack / pack.

Para as variantes de TNT do TA: Kingdoms (com outra palavra IDVersion), veja as notas de investigação sobre os mapas do TA:K.


Ver também

  • SCT — os tijolos que o TAE monta para fazer os TNT.
  • TDF — as definições de elementos resolvidas pelo nome, e o formato do ficheiro .ota associado.
  • PAL — a paleta usada para renderizar os píxeis dos mosaicos e o minimapa.
  • GAF — os sprites dos elementos que kbot tnt preview compõe.
  • Mapas do TA:K — o processo de mapas do TA: Kingdoms, muito diferente.
  • Glossário — célula de atributos, nível do mar, célula vazia.