Todas las páginas de formatos ↓
Mapas y terreno

TNT: terreno del mapa

Fuente: coreprime/kbot @ 0ab97bf · Editar en GitHub

Un archivo .tnt es un mapa jugable completo: gráficos de casillas, alturas del terreno, colocación de elementos (árboles, rocas, nodos de hidratación, …), nivel del mar y un minimapa de 252×252, todo en un único archivo autosuficiente. La geometría no tiene dependencias externas: deja un .tnt en una carpeta del juego y se cargará. (El archivo .ota que lo acompaña solo contiene metadatos: nombre, descripción, pistas para la IA, posiciones de inicio).

El mapa Metal Heck renderizado con los sprites de los elementos y los marcadores de inicio
maps/metal heck.tnt: vista previa completa con los sprites de los elementos compuestos a partir de features/*.tdf
y los marcadores de posición de inicio del .ota acompañante

Minimapa incrustado    Mapa de alturas normalizado
Minimapa incrustado (izquierda) y cuadrícula de elevación normalizada (derecha).

Tip

Pruébalo tú mismo.

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 la referencia de kbot tnt de la CLI.

Desde 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)

De un vistazo

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

Cabecera (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

Existen tres palabras de versión. 0x2000 es el mapa de Total Annihilation que escriben todas las herramientas. 0x1020 es una disposición más antigua de TA que TA 3.1c todavía lee: sus registros de atributos son de 8 bytes (la altura y después un elemento de un byte en el byte 2, donde 0xFC y superiores significan ninguno), y el puntero y las marcas de su minimapa están en 0x38/0x3c; kbot lo lee, y al guardarlo escribe 0x2000. 0x4000 es un mapa de TA: Kingdoms, que TA no puede cargar (consulta Mapas de TA:K); kbot tnt describe, la herramienta MCP tnt_describe y el selector Open Map del studio lo indican.

Important

Width y Height están en celdas de atributos de 16 píxeles, no en casillas. Divide por 2 para obtener el número de casillas; multiplica por 16 para obtener las dimensiones en píxeles. Así, un mapa de 4096×4096 px tiene Width = Height = 256 (celdas) y 128 × 128 = 16384 casillas en su cuadrícula de índices.


Cuadrícula de índices de casilla (PtrMapData)

(Width/2) × (Height/2) entradas uint16, por filas (Y exterior, X interior). Cada entrada es un índice en el array de gráficos de casilla. Las casillas miden 32 × 32 px; la cuadrícula es el mosaico que el juego vuelca para dibujar el terreno de juego.

Note

Las herramientas de Cavedog rellenan este bloque hasta un límite de 16 bytes con memoria de trabajo. kbot tnt unpack guarda los bytes de relleno tal cual en metadata.json (cuando se indica --lossless) para que el viaje de ida y vuelta sea idéntico byte a byte.

Cuadrícula de atributos (PtrMapAttr)

Width × Height celdas de 4 bytes cada una, por filas. En cada celda:

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;

El juego solo coloca un elemento cuando la palabra es menor que el número de entradas de la tabla de elementos (TileAnims); 0xFFFC marca una celda vacía y cualquier otra palabra no coloca nada. Las palabras desde 0xFFFB hacia arriba son valores centinela sea cual sea el tamaño de la tabla, así que una tabla debe tener menos de 0xFFFB entradas. kbot tnt describe, kbot tnt features y el explorador de recursos solo cuentan las colocaciones que hace el juego.

La cuadrícula de atributos es el doble de densa que la de casillas en cada dimensión: cada casilla tiene 4 celdas de atributos, una por subcasilla de 16 píxeles. Eso es lo que permite al motor resolver la búsqueda de rutas de las unidades, la colocación de construcciones y las colisiones de las armas con una granularidad de 16 píxeles.

Warning

0xFFFE y 0xFFFD aparecen en contenido de la versión comercial (Lava Run, Metal Heck y un par de mapas de campaña tempranos). No son índices de elemento ni celdas vacías: el juego no coloca nada ahí y en esas celdas se puede seguir construyendo. kbot conserva el valor original en el viaje de ida y vuelta.

Gráficos de casilla (PtrTileGfx)

Tiles × 1024 bytes de índices de la paleta, de arriba abajo y de izquierda a derecha dentro de cada bloque de 32×32, con los bloques guardados uno detrás de otro. El mapa lleva incrustado todo su arte de casillas: deja un TNT en la carpeta del juego y se puede jugar.

Nombres de 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;

Estos nombres se resuelven contra las secciones [FEATURE] de features/**/*.tdf (consulta TDF): así sabe el motor qué malla .3do y qué sprite .gaf tiene que dibujar para «MetalTower10» o «ArmTreeFoo». La entrada TDF del elemento lleva además la huella, el daño, el valor de recuperación, etc.

Note

Las herramientas de Cavedog dejan bytes de trabajo sin inicializar en la parte final de cada Name[128], después del terminador NUL. kbot tnt unpack (por defecto) escribe solo la parte imprimible en features.csv; el viaje de ida y vuelta reconstruye tablas limpias. Pasa --lossless para conservar exactamente los bytes en bruto y obtener un empaquetado 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

El juego solo lee el minimapa guardado cuando el bit 0 de la palabra de cabecera en 0x2c (MinimapFlags; 0x3c en un mapa 0x1020) está activado; si no, construye la imagen del radar a partir de las casillas. Los 275 mapas de la versión comercial lo activan, y también todos los mapas que guarda el studio. El radar del juego solo usa un minimapa guardado cuando sus dos lados miden al menos 252.

El mapa se dibuja en la esquina superior izquierda del minimapa y el resto es relleno, con el índice de la paleta 0x64 (unos cuantos mapas de campaña rellenan con 0). La región muestra el mapa visible, que es 32 píxeles más estrecho y 128 píxeles más bajo que las casillas (el juego nunca muestra esos bordes): su lado más largo mide 252 y el otro conserva la proporción del mapa visible, redondeada hacia abajo:

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

Así, 225×196 casillas dan una región de 252×216, 128×96 (ac04) da 252×182 y 96×128 da 193×252. La región la fija el tamaño del mapa, no los píxeles: algunos minimapas de la versión comercial no tienen ningún relleno (ac01 y de ac04 a ac11 llenan 252×256), y la región sigue siendo la calculada. Map.MinimapContentBounds y MinimapContentSize de kbot-io la calculan; el explorador de recursos coloca en ella los marcadores StartPos, y el studio guarda un minimapa construido de esta manera (Map.BuildMinimap: cada píxel del minimapa es el color medio de los píxeles del mapa que cubre).


Ejemplo desarrollado: 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
  ...

Cómo leerlo: Metal Heck es un mapa de 4192×4192 píxeles hecho con 583 casillas únicas, con 74 elementos colocados a partir de 28 definiciones de elemento distintas. El archivo comprimido del mundo «Metal» aporta las definiciones .tdf de esos elementos.


Desempaquetar y editar

kbot tnt unpack <file.tnt> --target <dir> produce una estructura que el comando pack correspondiente vuelve a leer:

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

Edita cualquiera de ellos y vuelve a empaquetar:

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

Important

El desempaquetado por defecto pierde información. La tabla de nombres de elementos no se guarda en metadata.json y se reconstruye al empaquetar a partir de los nombres únicos de features.csv. Así se pierden los bytes de trabajo finales que las herramientas de Cavedog dejaron en la tabla. Para un viaje de ida y vuelta idéntico byte a byte, pasa --lossless para guardar los bytes en bruto de los elementos:

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

Archivos .ota acompañantes

Cada .tnt se distribuye con un .ota ("Online Total Annihilation") acompañante en el mismo directorio. Ahí están los metadatos de la misión: nombre legible, nombre del planeta, meteorología, gravedad, posiciones de inicio, instrucciones para la IA, etc.

Consulta TDF: .ota es el mismo formato de texto al estilo INI que .fbi y .tdf. kbot tnt preview lo lee automáticamente para dibujar los marcadores numerados de las posiciones de inicio en la imagen de vista previa. Todas las vistas de kbot de un mapa leen los esquemas y las posiciones de inicio del .ota igual que el juego (consulta TDF: OTA); el editor de mapas del studio edita en el sitio el .ota del mapa abierto al guardar.


Escollos

Warning

El editor de mapas (TAE) escribe Height = actual_height + 6 en algunas versiones; la documentación de Saruman de 1997 lo señalaba: «raro, 6 más de los que se usan». Las herramientas modernas consideran que manda el tamaño de la cabecera e ignoran las filas finales sin uso. El lector de kbot se comporta igual.

  • SeaLevel == 0 NO significa «sin agua»: significa que el byte de altura 0 es la altura más alta bajo el agua, es decir, que nada está bajo el agua. Los mapas sin agua suelen usar SeaLevel = 1.
  • Los índices de casilla son uint16, así que un solo mapa puede tener hasta 65 536 casillas únicas, pero en la práctica cualquier cosa por encima de unos pocos miles llevará al motor a quedarse sin memoria y tirar de swap. Metal Heck (583 casillas) está más cerca de la mediana que del extremo. El studio se niega a guardar un mapa que necesite más, y kbot tnt pack rechaza los valores de tilemap.csv que no sean índices de las casillas del mapa (como -1 o 70000, que darían la vuelta a 4464). Una celda que nombra una casilla más allá del conjunto de casillas se carga (el juego lee más allá de su conjunto de casillas); kbot tnt lint lo señala como bad-tile-index.
  • El relleno del minimapa es el índice de la paleta 0x64; el motor ignora los píxeles fuera de la región del mapa.
  • El bit 0 de MinimapFlags (0x2c, o 0x3c en un mapa 0x1020) tiene que estar activado, o el juego ignora el minimapa guardado. Deja los demás bits como estaban.
  • Los cuatro campos pad son siempre cero. Algunos editores de terceros escriben valores distintos de cero; el juego parece ignorarlos, pero kbot conservará lo que encuentre.
  • TA: Kingdoms usa un formato de mapa relacionado pero incompatible (mapas de alturas .btm / .bmp + páginas de casillas). Consulta Mapas de TA:K.

Tamaños habituales

Recurso Rango observado en los maps/*.tnt de Cavedog
Tamaño del archivo 0,3 – 7 MB
Dimensiones del mapa (casillas) 64×64 – 256×256 (es decir, lados de 2048 – 8192 px)
Casillas únicas 200 – 2000
Elementos colocados 10 – 600
Definiciones de elemento en la tabla 5 – 50
Sobrecarga de la cabecera siempre 64 bytes
Minimapa (252×252 con paleta; 33 mapas de la versión comercial 252×256) 63 504 bytes
Tamaño del texto del .ota acompañante 1 – 8 KB

Ejemplos reales en el catálogo de referencia

Mapas interesantes para estudiar; extráelos de cualquier instalación de TA en maps/<name>.tnt:

  • metal heck.tnt: el del ejemplo desarrollado de arriba. 4192×4192 px, 583 casillas únicas, 74 elementos colocados. Un mapa emblemático y rico en recursos.
  • a gentle time.tnt: un mapa pequeño de 4 jugadores; buen punto de partida para hacer un prototipo de visor de TNT o de proceso de renderizado.
  • lava run.tnt: tiene centinelas de elemento 0xFFFE poco habituales en las celdas de atributos (es uno de los mapas que plantearon los casos límite al principio de la ingeniería inversa).
  • acid foursome.tnt: un mapa grande de 8 jugadores; muestra posiciones de inicio de [Schema 0] muy separadas en su .ota acompañante.
  • example.tnt: el TNT distribuido más pequeño (~263 KB); el mapa mínimo viable para probar el viaje de ida y vuelta de kbot tnt unpack / pack.

Para las variantes de TNT de TA: Kingdoms (con otra palabra IDVersion), consulta las notas de investigación sobre los mapas de TA:K.


Véase también

  • SCT: las piezas con las que TAE monta los TNT.
  • TDF: las definiciones de elementos, que se resuelven por nombre, y el formato del archivo .ota acompañante.
  • PAL: la paleta con la que se renderizan los píxeles de las casillas y el minimapa.
  • GAF: los sprites de elementos que compone kbot tnt preview.
  • Mapas de TA:K: el sistema de mapas de TA: Kingdoms, muy distinto.
  • Glosario: attribute cell, sea level, void cell.