TNT: terreno del mapa
Fuente: coreprime/kbot @ 0ab97bf · Editar en GitHub
Un archivo
.tntes 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.tnten una carpeta del juego y se cargará. (El archivo.otaque lo acompaña solo contiene metadatos: nombre, descripción, pistas para la IA, posiciones 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 (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 == 0NO 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 usarSeaLevel = 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, ykbot tnt packrechaza los valores detilemap.csvque no sean índices de las casillas del mapa (como-1o70000, 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 lintlo señala comobad-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, o0x3cen un mapa0x1020) tiene que estar activado, o el juego ignora el minimapa guardado. Deja los demás bits como estaban. - Los cuatro campos
padson 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 elemento0xFFFEpoco 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.otaacompañ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 dekbot 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
.otaacompañ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.