Tutte le pagine dei formati ↓
Mappe e terreno

TNT — Terreno della mappa

Fonte: coreprime/kbot @ 0ab97bf · Modifica su GitHub

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 .tnt in una cartella del gioco e verrà caricato. (Il file .ota che lo accompagna contiene solo metadati: nome, descrizione, suggerimenti per l'IA, posizioni di partenza.)

Mappa Metal Heck disegnata con gli sprite degli elementi e gli indicatori 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    Mappa delle altezze normalizzata
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 == 0 NON 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 solito SeaLevel = 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ù, e kbot tnt pack rifiuta i valori di tilemap.csv che non sono indici dei tile della mappa (come -1 o 70000, 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 lint la segnala come bad-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, o 0x3c in una mappa 0x1020) deve essere impostato, altrimenti il gioco ignora la minimappa memorizzata. Lascia gli altri bit come li trovi.
  • I quattro campi pad sono 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 sentinella 0xFFFE per 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 .ota gemello.
  • example.tnt: il TNT più piccolo distribuito (~263 KB); la mappa minima indispensabile per provare il round-trip kbot 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).