TNT — terrain des cartes
Source : coreprime/kbot @ 0ab97bf · Modifier sur GitHub
Un fichier
.tntest 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.tntdans un dossier de jeu et il se chargera. (Le fichier.otaqui l'accompagne ne contient que des métadonnées : nom, description, indications pour l'IA, positions 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 (à 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 == 0ne 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éralSeaLevel = 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, etkbot tnt packrefuse les valeurs detilemap.csvqui ne sont pas des index de tuiles de la carte (comme-1ou70000, 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 lintla signale commebad-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, ou0x3cdans une carte0x1020) 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
padvalent 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éments0xFFFEinhabituelles 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.otaassocié.example.tnt— le plus petit TNT livré (~263 Ko) ; la carte minimale viable pour tester l'aller-retourkbot 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
.otaassocié. - 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.