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.tntnuma pasta do jogo para ele carregar. (O ficheiro.otaque o acompanha só tem metadados: nome, descrição, indicações para a IA, 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 (à 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 == 0NÃ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 normalmenteSeaLevel = 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, ekbot tnt packrecusa valores detilemap.csvque não sejam índices dos mosaicos do mapa (como-1ou70000, 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 lintassinala-a comobad-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, ou0x3cnum mapa0x1020) tem de estar definido, ou o jogo ignora o minimapa guardado. Mantenha os outros bits como os encontrou. - Os quatro campos
padsã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 elementos0xFFFEinvulgares 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.otaassociado.example.tnt— o TNT mais pequeno incluído no jogo (~263 KB); o mapa mínimo viável para testar a ida e volta dekbot 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
.otaassociado. - PAL — a paleta usada para renderizar os píxeis dos mosaicos e o minimapa.
- GAF — os sprites dos elementos que
kbot tnt previewcompõ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.