TNT — Terreno della mappa
Un file
.tntè una mappa giocabile completa: grafica dei tile, altezze del terreno, posizionamenti degli elementi (alberi, rocce, nodi d'idratazione, …), livello del mare e una minimappa 252×252, tutto in un unico file autosufficiente. La geometria non ha dipendenze esterne: metti un.tntin una cartella del gioco e verrà caricato. (Il file.otache lo accompagna contiene solo metadati: nome, descrizione, suggerimenti per l'IA, posizioni di partenza.)
maps/metal heck.tnt — anteprima completa con gli sprite degli elementi composti da features/*.tdf
e gli indicatori delle posizioni di partenza dal .ota gemello
Minimappa incorporata (a sinistra) e griglia delle elevazioni normalizzata (a destra).
Tip
Provalo tu stesso.
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
Consulta il riferimento CLI di kbot tnt.
Da Go. Usa 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)
In breve
┌─ 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) ─┐
└──────────────────────────────┘
Intestazione (64 byte)
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
Esistono tre parole di versione. 0x2000 è la mappa di Total
Annihilation che scrivono tutti gli strumenti. 0x1020 è una struttura
più vecchia di TA che TA 3.1c legge ancora: i suoi record degli attributi
sono di 8 byte (l'altezza, poi un elemento di un byte al byte 2, dove
0xFC e oltre significano nessuno), e il puntatore e i flag della sua
minimappa stanno a 0x38/0x3c; kbot la legge, e salvandola scrive
0x2000. 0x4000 è una mappa di TA: Kingdoms, che TA non può caricare
(vedi Mappe TA:K); kbot tnt describe, lo strumento MCP
tnt_describe e il selettore Open Map dello studio lo segnalano.
Important
Width e Height sono espressi in celle degli attributi da 16 pixel,
non in tile. Dividi per 2 per ottenere il numero di tile; moltiplica per
16 per le dimensioni in pixel. Così una mappa di 4096×4096 px ha
Width = Height = 256 (celle) e 128 × 128 = 16384 tile nella sua
griglia di indici.
Griglia degli indici dei tile (PtrMapData)
(Width/2) × (Height/2) voci uint16, per righe (Y esterno, X interno).
Ogni voce è un indice nell'array della grafica dei tile. I tile sono di
32 × 32 px; la griglia è il mosaico che il gioco copia sullo schermo (blit)
per disegnare il campo di gioco.
Note
Gli strumenti di Cavedog riempiono questo blocco fino a un confine di 16
byte con residui di memoria. kbot tnt unpack registra i byte di
riempimento tali e quali in metadata.json (quando è impostato
--lossless), così il round-trip è identico byte per byte.
Griglia degli attributi (PtrMapAttr)
Width × Height celle di 4 byte ciascuna, per righe. Per ogni cella:
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;
Il gioco piazza un elemento solo per una parola inferiore al numero di voci
della tabella degli elementi (TileAnims); 0xFFFC indica una cella vuota
(void), e ogni altra parola non piazza nulla. Le parole da 0xFFFB in su
sono valori sentinella qualunque sia la dimensione della tabella, quindi una
tabella deve contenere meno di 0xFFFB voci. kbot tnt describe,
kbot tnt features e l'esploratore di asset contano solo i posizionamenti
che il gioco effettua davvero.
La griglia degli attributi è più fitta di quella dei tile di 2× in ciascuna dimensione: ogni tile ha 4 celle degli attributi, una per ogni sotto-tile da 16 pixel. È questo che permette al motore di risolvere il pathfinding delle unità, il posizionamento delle costruzioni e le collisioni delle armi con una granularità di 16 pixel.
Warning
0xFFFE e 0xFFFD compaiono nei contenuti originali (Lava Run, Metal
Heck e un paio delle prime mappe della campagna). Non sono indici di
elementi e non sono celle vuote: il gioco non vi piazza nulla e le celle
restano edificabili. kbot conserva il valore originale nel round-trip.
Grafica dei tile (PtrTileGfx)
Tiles × 1024 byte di indici della palette, dall'alto in basso e da
sinistra a destra all'interno di ciascun blocco 32×32, con i blocchi
memorizzati uno dopo l'altro. La mappa contiene inline tutta la grafica
dei suoi tile: metti un TNT nella cartella del gioco e la mappa è pronta per
una partita.
Nomi degli elementi (PtrTileAnim)
TileAnims voci di questo tipo:
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;
Questi nomi si risolvono nelle sezioni [FEATURE] di features/**/*.tdf
(vedi TDF): è così che il motore sa quale mesh .3do e quale
sprite .gaf disegnare per "MetalTower10" o "ArmTreeFoo". La voce TDF
dell'elemento contiene anche ingombro, danno, valore di recupero, ecc.
Note
Gli strumenti di Cavedog lasciano byte residui non inizializzati nella
parte finale di ogni Name[128], dopo il terminatore NUL. kbot tnt unpack (per impostazione predefinita) scrive in features.csv solo la
parte stampabile; il round-trip ricostruisce tabelle pulite. Passa
--lossless per conservare esattamente i byte grezzi e ottenere un
impacchettamento identico byte per byte.
Minimappa (PtrMiniMap)
uint32 Width; // 252
uint32 Height; // 252 (33 of the 275 retail maps store 256)
uint8 pixels[Width * Height]; // Palette indices
Il gioco legge la minimappa memorizzata solo quando è impostato il bit 0
della parola dell'intestazione a 0x2c (MinimapFlags; 0x3c in una mappa
0x1020); altrimenti costruisce l'immagine del radar a partire dai tile.
Tutte le 275 mappe originali lo impostano, e così ogni mappa salvata dallo
studio. Il radar del gioco usa una minimappa memorizzata solo quando
entrambi i suoi lati sono almeno 252.
La mappa viene disegnata nell'angolo in alto a sinistra della minimappa
e il resto è riempimento, con l'indice della palette 0x64 (alcune mappe
della campagna riempiono con 0). La regione mostra la mappa visibile,
che è 32 pixel più stretta e 128 pixel più bassa dei tile (il gioco non
mostra mai quei bordi): il suo lato più lungo è 252 e l'altro mantiene le
proporzioni della mappa visibile, arrotondato per difetto:
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; }
Così 225×196 tile danno una regione di 252×216, 128×96 (ac04) dà 252×182
e 96×128 dà 193×252. La regione è fissata dalle dimensioni della mappa, non
dai pixel: alcune minimappe originali non hanno alcun riempimento (ac01 e
da ac04 a ac11 riempiono 252×256), e la regione resta quella calcolata.
Map.MinimapContentBounds e MinimapContentSize di kbot-io la calcolano;
l'esploratore di asset vi colloca gli indicatori StartPos, e lo studio salva
una minimappa costruita in questo modo (Map.BuildMinimap: ogni pixel della
minimappa è il colore medio dei pixel della mappa che copre).
Esempio svolto — 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
...
Come leggerlo: Metal Heck è una mappa di 4192×4192 pixel fatta di 583 tile
unici, con 74 posizionamenti di elementi tratti da 28 definizioni di
elementi distinte. L'archivio del mondo "Metal" fornisce le definizioni
.tdf di quegli elementi.
Estrarre e modificare
kbot tnt unpack <file.tnt> --target <dir> produce una struttura che il
comando pack corrispondente sa rileggere:
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
Modifica uno qualsiasi di questi file e reimpacchetta:
kbot tnt pack ./metal_heck --target metal_heck.tnt
Important
L'estrazione predefinita perde informazioni. La tabella dei nomi degli
elementi viene esclusa da metadata.json e ricostruita al momento del
pack a partire dai nomi unici in features.csv. Così si perdono gli
eventuali byte residui finali che gli strumenti di Cavedog hanno lasciato
nella tabella. Per round-trip identici byte per byte, passa --lossless
per registrare i byte grezzi degli elementi:
kbot tnt unpack "metal heck.tnt" -t ./metal_heck --lossless
File .ota gemelli
Ogni .tnt viene distribuito con un file di accompagnamento .ota
("Online Total Annihilation") nella stessa directory. Lì vivono i metadati
della missione: nome leggibile, nome del pianeta, meteo, gravità, posizioni
di partenza, istruzioni per l'IA, ecc.
Vedi TDF: il .ota usa lo stesso formato testuale in stile INI
di .fbi e .tdf. kbot tnt preview lo legge automaticamente per
disegnare nell'immagine di anteprima gli indicatori numerati delle posizioni
di partenza. Ogni vista di kbot su una mappa legge gli schemi e le posizioni
di partenza del .ota come fa il gioco (vedi
TDF — OTA); al salvataggio, l'editor di mappe
dello studio modifica direttamente il .ota della mappa aperta.
Insidie
Warning
L'editor di mappe (TAE) scrive Height = actual_height + 6 in alcune
versioni: la documentazione di Saruman del 1997 annotava "strano, 6 in più
di quelle usate". Gli strumenti moderni considerano le dimensioni
dell'intestazione come autorevoli e ignorano le righe finali inutilizzate.
Il lettore di kbot si comporta allo stesso modo.
SeaLevel == 0NON significa "niente acqua": significa che il byte di altezza 0 è la quota subacquea più alta, cioè che niente è sott'acqua. Le mappe senza acqua usano di solitoSeaLevel = 1.- Gli indici dei tile sono
uint16, quindi una singola mappa può avere fino a 65.536 tile unici; in pratica, però, qualunque cosa oltre qualche migliaio spinge il motore a fare swap per mancanza di memoria. Metal Heck (583 tile) è più vicina alla mediana che alla coda. Lo studio si rifiuta di salvare una mappa che ne richiede di più, ekbot tnt packrifiuta i valori ditilemap.csvche non sono indici dei tile della mappa (come-1o70000, che si ridurrebbe a 4464). Una cella che indica un tile oltre la fine del set di tile si carica (il gioco legge oltre il suo set di tile);kbot tnt lintla segnala comebad-tile-index. - Il riempimento della minimappa è l'indice della palette
0x64; il motore ignora i pixel fuori dalla regione della mappa. - Il bit 0 di
MinimapFlags(0x2c, o0x3cin una mappa0x1020) deve essere impostato, altrimenti il gioco ignora la minimappa memorizzata. Lascia gli altri bit come li trovi. - I quattro campi
padsono sempre zero. Alcuni editor di terze parti scrivono valori diversi da zero; il gioco sembra ignorarli, ma kbot conserva qualunque valore trovi. - TA: Kingdoms usa un formato di mappa affine ma incompatibile (mappe
delle altezze
.btm/.bmp+ pagine di tile). Vedi Mappe TA:K.
Dimensioni tipiche
| Elemento | Intervallo osservato in maps/*.tnt di Cavedog |
|---|---|
| Dimensione del file | 0,3 – 7 MB |
| Dimensioni della mappa (tile) | 64×64 – 256×256 (cioè lati da 2048 – 8192 px) |
| Tile unici | 200 – 2000 |
| Posizionamenti di elementi | 10 – 600 |
| Definizioni di elementi nella tabella | 5 – 50 |
| Costo dell'intestazione | sempre 64 byte |
| Minimappa (252×252 con palette; 33 mappe originali 252×256) | 63504 byte |
Dimensione del testo del .ota che la accompagna |
1 – 8 KB |
Esempi reali nel catalogo di riferimento
Mappe degne di studio, da estrarre da qualsiasi installazione di TA in
maps/<name>.tnt:
metal heck.tnt: protagonista dell'esempio svolto qui sopra. 4192×4192 px, 583 tile unici, 74 posizionamenti di elementi. Una mappa iconica, ricca di risorse.a gentle time.tnt: piccola mappa per 4 giocatori; un buon punto di partenza quando si prototipa un visualizzatore di TNT o una pipeline di rendering.lava run.tnt: ha insoliti valori sentinella0xFFFEper gli elementi nelle celle degli attributi (una delle mappe da cui sono nati i casi limite del primo reverse engineering).acid foursome.tnt: grande mappa per 8 giocatori, con le posizioni di partenza di[Schema 0]molto distanziate nel.otagemello.example.tnt: il TNT più piccolo distribuito (~263 KB); la mappa minima indispensabile per provare il round-tripkbot tnt unpack/pack.
Per le varianti TNT di TA: Kingdoms (con una diversa parola IDVersion), vedi le note di ricerca sulle mappe di TA:K.
Vedi anche
- SCT: i mattoni che TAE assembla nei TNT.
- TDF: le definizioni degli elementi, risolte per nome, e il
formato del file gemello
.ota. - PAL: la palette usata per disegnare i pixel dei tile e la minimappa.
- GAF: gli sprite degli elementi composti da
kbot tnt preview. - Mappe TA:K: la pipeline delle mappe di TA: Kingdoms, molto diversa.
- Glossario: cella degli attributi (attribute cell), livello del mare (sea level), cella vuota (void cell).