Toutes les pages de formats ↓
Cartes et terrain

TNT — terrain des cartes

Source : coreprime/kbot @ 0ab97bf · Modifier sur GitHub

Un fichier .tnt est une carte jouable complète : graphismes des tuiles, hauteurs du terrain, placement des éléments de décor (arbres, rochers, points d'hydratation, …), niveau de la mer et minicarte de 252×252, le tout dans un seul fichier autonome. La géométrie ne dépend de rien d'externe : déposez un .tnt dans un dossier de jeu et il se chargera. (Le fichier .ota qui l'accompagne ne contient que des métadonnées : nom, description, indications pour l'IA, positions de départ.)

Carte Metal Heck rendue avec les sprites des éléments et les marqueurs de départ
maps/metal heck.tnt — aperçu complet avec les sprites des éléments composés d'après features/*.tdf
et les marqueurs de position de départ tirés du .ota associé

Minicarte intégrée    Carte des hauteurs normalisée
Minicarte intégrée (à gauche) et grille d'altitudes normalisée (à droite).

Tip

Essayez vous-même.

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

Consultez la référence de kbot tnt de la CLI.

Depuis Go. Utilisez 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)

Vue d'ensemble

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

En-tête (64 octets)

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

Il existe trois mots de version. 0x2000 est la carte Total Annihilation qu'écrivent tous les outils. 0x1020 est une ancienne disposition de TA que TA 3.1c sait encore lire : ses enregistrements d'attributs font 8 octets (la hauteur, puis un élément de décor sur un octet à l'octet 2, 0xFC et au-delà signifiant « aucun »), et son pointeur de minicarte et ses flags se trouvent en 0x38/0x3c ; kbot la lit, et l'enregistrer écrit du 0x2000. 0x4000 est une carte TA: Kingdoms, que TA ne peut pas charger (voir Cartes TA:K) ; kbot tnt describe, l'outil MCP tnt_describe et le sélecteur Open Map du studio le signalent.

Important

Width et Height sont exprimés en cases d'attributs de 16 pixels, pas en tuiles. Divisez par 2 pour obtenir le nombre de tuiles ; multipliez par 16 pour les dimensions en pixels. Une carte de 4 096 × 4 096 px a donc Width = Height = 256 (cases) et 128 × 128 = 16384 tuiles dans sa grille d'index.


Grille d'index des tuiles (PtrMapData)

(Width/2) × (Height/2) entrées uint16, ligne par ligne (Y à l'extérieur, X à l'intérieur). Chaque entrée est un index dans le tableau des graphismes de tuiles. Les tuiles font 32 × 32 px ; la grille est la mosaïque que le jeu copie à l'écran pour dessiner le terrain de jeu.

Note

Les outils de Cavedog complètent ce bloc jusqu'à une frontière de 16 octets avec le contenu d'une mémoire de travail. kbot tnt unpack enregistre ces octets de remplissage tels quels dans metadata.json (avec --lossless), pour que l'aller-retour soit identique à l'octet près.

Grille d'attributs (PtrMapAttr)

Width × Height cases de 4 octets chacune, ligne par ligne. Pour chaque case :

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;

Le jeu ne place un élément de décor que pour un mot inférieur au nombre d'entrées de la table des éléments (TileAnims) ; 0xFFFC marque une case vide, et tout autre mot ne place rien. Les mots à partir de 0xFFFB sont des sentinelles quelle que soit la taille de la table : une table doit donc contenir moins de 0xFFFB entrées. kbot tnt describe, kbot tnt features et l'explorateur de ressources ne comptent que les placements que fait le jeu.

La grille d'attributs est deux fois plus dense que la grille des tuiles dans chaque dimension : chaque tuile a 4 cases d'attributs, une par sous-tuile de 16 pixels. C'est ce qui permet au moteur de résoudre la recherche de chemin des unités, le placement des constructions et les collisions des armes avec une granularité de 16 pixels.

Warning

0xFFFE et 0xFFFD apparaissent dans le contenu d'origine (Lava Run, Metal Heck et quelques-unes des premières cartes de campagne). Ce ne sont ni des index d'éléments ni des cases vides : le jeu n'y place rien et les cases restent constructibles. kbot conserve la valeur d'origine lors d'un aller-retour.

Graphismes des tuiles (PtrTileGfx)

Tiles × 1024 octets d'index de palette, de haut en bas et de gauche à droite dans chaque bloc de 32×32, les blocs étant stockés à la suite. La carte embarque tous les graphismes de ses tuiles : déposez un TNT dans le dossier du jeu, et il est jouable.

Noms des éléments de décor (PtrTileAnim)

TileAnims entrées de la forme :

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;

Ces noms se résolvent vers les sections [FEATURE] de features/**/*.tdf (voir TDF) : c'est ainsi que le moteur sait quel maillage .3do et quel sprite .gaf dessiner pour « MetalTower10 » ou « ArmTreeFoo ». L'entrée TDF de l'élément porte aussi son emprise, ses dégâts, sa valeur de récupération, etc.

Note

Les outils de Cavedog laissent des octets de travail non initialisés dans la partie finale de chaque Name[128], après le NUL terminal. kbot tnt unpack (par défaut) n'écrit que la partie imprimable dans features.csv ; l'aller-retour reconstruit des tables propres. Passez --lossless pour conserver exactement les octets bruts et obtenir un empaquetage identique à l'octet près.

Minicarte (PtrMiniMap)

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

Le jeu ne lit la minicarte stockée que si le bit 0 du mot d'en-tête en 0x2c (MinimapFlags ; 0x3c dans une carte 0x1020) est à un ; sinon, il construit l'image radar à partir des tuiles. Les 275 cartes d'origine le positionnent toutes, de même que chaque carte enregistrée par le studio. Le radar du jeu n'utilise une minicarte stockée que si ses deux côtés mesurent au moins 252.

La carte est dessinée dans le coin supérieur gauche de la minicarte, et le reste est du remplissage, l'index de palette 0x64 (quelques cartes de campagne remplissent avec 0). Cette zone montre la carte visible, qui est plus étroite de 32 pixels et moins haute de 128 pixels que les tuiles (le jeu n'affiche jamais ces bords) : son grand côté mesure 252 et l'autre conserve les proportions de la carte visible, arrondi par défaut :

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

Ainsi, 225×196 tuiles donnent une zone de 252×216, 128×96 (ac04) donne 252×182 et 96×128 donne 193×252. La zone est fixée par la taille de la carte, pas par les pixels : certaines minicartes d'origine n'ont aucun remplissage (ac01, et ac04 à ac11 remplissent 252×256), et la zone reste celle qui est calculée. Map.MinimapContentBounds et MinimapContentSize de kbot-io la calculent ; l'explorateur de ressources y place les marqueurs StartPos, et le studio enregistre une minicarte construite de cette façon (Map.BuildMinimap : chaque pixel de la minicarte est la couleur moyenne des pixels de la carte qu'il couvre).


Exemple commenté — 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
  ...

Lecture : Metal Heck est une carte de 4 192 × 4 192 pixels construite à partir de 583 tuiles uniques, avec 74 placements d'éléments de décor tirés de 28 définitions distinctes. L'archive du monde « Metal » fournit les définitions .tdf de ces éléments.


Dépaqueter et modifier

kbot tnt unpack <file.tnt> --target <dir> produit une arborescence que la commande pack correspondante sait relire :

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

Modifiez n'importe lequel de ces fichiers, puis réempaquetez :

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

Important

Le dépaquetage par défaut perd de l'information. La table des noms d'éléments est retirée de metadata.json et reconstruite à l'empaquetage à partir des noms uniques de features.csv. On perd ainsi les octets de travail que les outils de Cavedog avaient laissés en fin d'entrée dans la table. Pour des allers-retours identiques à l'octet près, passez --lossless afin d'enregistrer les octets bruts des éléments :

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

Les fichiers .ota associés

Chaque .tnt est livré avec un fichier compagnon .ota (« Online Total Annihilation ») dans le même répertoire. C'est là que se trouvent les métadonnées de mission : nom lisible, nom de la planète, météo, gravité, positions de départ, consignes pour l'IA, etc.

Voir TDF : le .ota utilise le même format texte de style INI que .fbi et .tdf. kbot tnt preview le lit automatiquement pour dessiner les marqueurs numérotés des positions de départ dans l'image d'aperçu. Toutes les vues de kbot sur une carte lisent les schémas et les positions de départ du .ota comme le fait le jeu (voir TDF — OTA) ; l'éditeur de cartes du studio modifie sur place, à l'enregistrement, le .ota de la carte ouverte.


Pièges

Warning

L'éditeur de cartes (TAE) écrit Height = actual_height + 6 dans certaines versions — la documentation de Saruman de 1997 le notait déjà : « bizarre, 6 de plus que ce qui est utilisé ». Les outils modernes considèrent les dimensions de l'en-tête comme faisant foi et ignorent les lignes finales inutilisées. Le lecteur de kbot se comporte de même.

  • SeaLevel == 0 ne signifie PAS « pas d'eau » : cela signifie que l'octet de hauteur 0 est la plus haute hauteur immergée, autrement dit que rien n'est sous l'eau. Les cartes sans eau utilisent en général SeaLevel = 1.
  • Les index de tuiles sont des uint16 : une même carte peut donc avoir jusqu'à 65 536 tuiles uniques — mais en pratique, au-delà de quelques milliers, le moteur manque de mémoire et se met à paginer. Metal Heck (583 tuiles) est plus proche de la médiane que des extrêmes. Le studio refuse d'enregistrer une carte qui en demande davantage, et kbot tnt pack refuse les valeurs de tilemap.csv qui ne sont pas des index de tuiles de la carte (comme -1 ou 70000, qui reboucleraient sur 4 464). Une case qui désigne une tuile au-delà du jeu de tuiles se charge (le jeu lit au-delà de son jeu de tuiles) ; kbot tnt lint la signale comme bad-tile-index.
  • Le remplissage de la minicarte est l'index de palette 0x64 ; le moteur ignore les pixels situés hors de la zone de la carte.
  • Le bit 0 de MinimapFlags (0x2c, ou 0x3c dans une carte 0x1020) doit être à un, sinon le jeu ignore la minicarte stockée. Laissez les autres bits tels que vous les avez trouvés.
  • Les quatre champs pad valent toujours zéro. Certains éditeurs tiers y écrivent des valeurs non nulles ; le jeu semble les ignorer, mais kbot conserve ce qu'il trouve.
  • TA: Kingdoms utilise un format de carte apparenté mais incompatible (cartes des hauteurs .btm / .bmp + pages de tuiles). Voir Cartes TA:K.

Tailles typiques

Ressource Plage observée dans les maps/*.tnt de Cavedog
Taille du fichier 0,3 – 7 Mo
Dimensions de la carte (tuiles) 64×64 – 256×256 (soit 2 048 – 8 192 px de côté)
Tuiles uniques 200 – 2 000
Placements d'éléments de décor 10 – 600
Définitions d'éléments dans la table 5 – 50
Surcoût de l'en-tête toujours 64 octets
Minicarte (252×252 à palette ; 252×256 pour 33 cartes d'origine) 63 504 octets
Taille du texte du .ota associé 1 – 8 Ko

Exemples concrets dans le catalogue de référence

Cartes remarquables à étudier — à extraire de n'importe quelle installation de TA, dans maps/<name>.tnt :

  • metal heck.tnt — celle de l'exemple commenté ci-dessus. 4 192 × 4 192 px, 583 tuiles uniques, 74 placements d'éléments. Carte emblématique riche en ressources.
  • a gentle time.tnt — petite carte à 4 joueurs ; un bon point de départ pour prototyper une visionneuse TNT ou une chaîne de rendu.
  • lava run.tnt — contient des sentinelles d'éléments 0xFFFE inhabituelles dans les cases d'attributs (l'une des cartes à l'origine des cas limites des débuts de la rétro-ingénierie).
  • acid foursome.tnt — grande carte à 8 joueurs, qui montre des positions de départ [Schema 0] très dispersées dans le .ota associé.
  • example.tnt — le plus petit TNT livré (~263 Ko) ; la carte minimale viable pour tester l'aller-retour kbot tnt unpack / pack.

Pour les variantes TNT de TA: Kingdoms (autre mot IDVersion), voir les notes de recherche sur les cartes TA:K.


Voir aussi

  • SCT — les briques que TAE assemble pour construire les TNT.
  • TDF — les définitions d'éléments de décor résolues par nom, et le format du fichier .ota associé.
  • PAL — la palette utilisée pour rendre les pixels des tuiles et la minicarte.
  • GAF — les sprites d'éléments composés par kbot tnt preview.
  • Cartes TA:K — la chaîne des cartes de TA: Kingdoms, très différente.
  • Glossaire — case d'attributs, niveau de la mer, case vide.