TNT – Kartengelände
Eine
.tnt-Datei ist eine vollständige spielbare Karte: Kachelgrafiken, Geländehöhen, Platzierungen von Kartenobjekten (Bäume, Felsen, Hydrationsknoten, …), Meeresspiegel und eine 252×252-Minikarte, alles in einer einzigen, in sich geschlossenen Datei. Für die Geometrie gibt es keine externen Abhängigkeiten – leg eine.tntin einen Spielordner, und sie lädt. (Die zugehörige.ota-Datei enthält nur Metadaten: Name, Beschreibung, Hinweise für die KI, Startpositionen.)
maps/metal heck.tnt – vollständige Vorschau mit Objekt-Sprites, zusammengesetzt aus features/*.tdf,
und Markierungen der Startpositionen aus der zugehörigen .ota
Eingebettete Minikarte (links) und normalisiertes Höhenraster (rechts).
Tip
Probier es selbst.
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
Siehe die kbot tnt-Referenz der CLI.
Aus Go heraus. Verwende 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)
Auf einen Blick
┌─ 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) ─┐
└──────────────────────────────┘
Header (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
Es gibt drei Versionswörter. 0x2000 ist die Karte von Total
Annihilation, die jedes Werkzeug schreibt. 0x1020 ist ein älterer
Aufbau von TA, den TA 3.1c noch liest: Seine Attribut-Datensätze sind
8 Byte groß (Höhe, dann ein Objekt als einzelnes Byte an Byte 2, wobei
0xFC und darüber „keines“ bedeutet), und Zeiger und Flags der
Minikarte stehen bei 0x38/0x3c; kbot liest ihn, und beim Speichern
wird 0x2000 geschrieben. 0x4000 ist eine Karte von TA: Kingdoms, die
TA nicht laden kann (siehe TA:K-Karten); kbot tnt describe,
das MCP-Werkzeug tnt_describe und die Kartenauswahl Open Map des Studios
weisen darauf hin.
Important
Width und Height zählen Attributzellen zu 16 Pixeln, keine
Kacheln. Teile durch 2, um die Kachelanzahl zu erhalten; multipliziere
mit 16 für die Maße in Pixeln. Eine Karte von 4.096×4.096 px hat also
Width = Height = 256 (Zellen) und 128 × 128 = 16384 Kacheln in ihrem
Indexraster.
Raster der Kachelindizes (PtrMapData)
(Width/2) × (Height/2) Einträge vom Typ uint16, zeilenweise (Y außen,
X innen). Jeder Eintrag ist ein Index in das Array der Kachelgrafiken.
Kacheln sind 32 × 32 px groß; das Raster ist das Mosaik, das das Spiel
blittet, um das Spielfeld zu zeichnen.
Note
Cavedogs Werkzeuge füllen diesen Block mit Speicherresten auf eine
16-Byte-Grenze auf. kbot tnt unpack hält die Füllbytes unverändert in
metadata.json fest (wenn --lossless gesetzt ist), damit der
Round-Trip byte-identisch ist.
Attributraster (PtrMapAttr)
Width × Height Zellen zu je 4 Byte, zeilenweise. Pro Zelle:
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;
Das Spiel platziert ein Kartenobjekt nur bei einem Wort unterhalb der
Größe der Objekttabelle (TileAnims); 0xFFFC kennzeichnet eine
Leerzelle, und jedes andere Wort platziert nichts. Wörter ab 0xFFFB sind
unabhängig von der Tabellengröße Sentinel-Werte, eine Tabelle muss also
weniger als 0xFFFB Einträge haben. kbot tnt describe,
kbot tnt features und der Asset-Explorer zählen nur die Platzierungen,
die das Spiel tatsächlich vornimmt.
Das Attributraster ist in jeder Richtung doppelt so fein wie das Kachelraster – jede Kachel hat 4 Attributzellen, eine pro Teilkachel zu 16 Pixeln. Dadurch kann die Engine Wegfindung, Bauplatzierung und Waffenkollisionen mit einer Auflösung von 16 Pixeln berechnen.
Warning
0xFFFE und 0xFFFD kommen in Inhalten der Verkaufsversion vor
(Lava Run, Metal Heck und ein paar frühe Kampagnenkarten). Sie sind
weder Objektindizes noch Leerzellen: Das Spiel platziert dort nichts, und
die Zellen bleiben bebaubar. kbot bewahrt beim Round-Trip den
ursprünglichen Wert.
Kachelgrafiken (PtrTileGfx)
Tiles × 1024 Byte Palettenindizes, innerhalb jedes 32×32-Blocks von oben
nach unten und von links nach rechts, die Blöcke lückenlos
hintereinander. Die Karte bringt ihre gesamte Kachelgrafik selbst mit –
leg eine TNT in den Spielordner, und sie ist spielbar.
Objektnamen (PtrTileAnim)
TileAnims Einträge der Form:
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;
Diese Namen werden über die [FEATURE]-Abschnitte in
features/**/*.tdf aufgelöst (siehe TDF) – so weiß die Engine,
welches .3do-Mesh und welches .gaf-Sprite sie für „MetalTower10“ oder
„ArmTreeFoo“ zeichnen muss. Der TDF-Eintrag des Kartenobjekts enthält
außerdem Grundfläche, Schaden, Rückgewinnungswert usw.
Note
Cavedogs Werkzeuge lassen im hinteren Teil jedes Name[128] nach dem
NUL-Terminator nicht initialisierte Speicherreste stehen.
kbot tnt unpack schreibt (standardmäßig) nur den druckbaren Teil in
features.csv; der Round-Trip baut saubere Tabellen neu auf. Mit
--lossless bleiben die rohen Bytes für ein byte-identisches Packen
exakt erhalten.
Minikarte (PtrMiniMap)
uint32 Width; // 252
uint32 Height; // 252 (33 of the 275 retail maps store 256)
uint8 pixels[Width * Height]; // Palette indices
Das Spiel liest die gespeicherte Minikarte nur, wenn Bit 0 des
Header-Worts bei 0x2c (MinimapFlags; 0x3c bei einer 0x1020-Karte)
gesetzt ist; andernfalls baut es das Radarbild aus den Kacheln auf. Alle
275 Karten der Verkaufsversion setzen es, ebenso jede Karte, die das Studio
speichert. Das Radar des Spiels verwendet eine gespeicherte Minikarte nur,
wenn beide Seiten mindestens 252 lang sind.
Die Karte wird in die linke obere Ecke der Minikarte gezeichnet, der
Rest ist Auffüllung mit dem Palettenindex 0x64 (ein paar Kampagnenkarten
füllen mit 0 auf). Der Bereich zeigt die sichtbare Karte, die 32
Pixel schmaler und 128 Pixel niedriger ist als die Kacheln (das Spiel
zeigt diese Ränder nie): Ihre längere Seite ist 252, die andere behält das
Seitenverhältnis der sichtbaren Karte, abgerundet:
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; }
225×196 Kacheln ergeben also einen Bereich von 252×216, 128×96 (ac04)
ergibt 252×182 und 96×128 ergibt 193×252. Der Bereich ergibt sich aus der
Größe der Karte, nicht aus den Pixeln: Manche Minikarten der
Verkaufsversion haben überhaupt keine Auffüllung (ac01, ac04 bis
ac11 füllen 252×256), und der Bereich ist trotzdem der berechnete.
Map.MinimapContentBounds und MinimapContentSize aus kbot-io berechnen
ihn; der Asset-Explorer setzt die StartPos-Markierungen hinein, und das
Studio speichert eine so erstellte Minikarte (Map.BuildMinimap: Jedes
Pixel der Minikarte ist die Durchschnittsfarbe der Kartenpixel, die es
abdeckt).
Beispiel im Detail – 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
...
So liest man das: Metal Heck ist eine Karte von 4.192×4.192 Pixeln aus 583
einzigartigen Kacheln, mit 74 Platzierungen von Kartenobjekten aus 28
verschiedenen Objektdefinitionen. Das Weltarchiv „Metal“ liefert die
.tdf-Definitionen für diese Kartenobjekte.
Entpacken und bearbeiten
kbot tnt unpack <file.tnt> --target <dir> erzeugt einen Aufbau, den der
passende Befehl pack wieder einliest:
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
Bearbeite irgendetwas davon und packe neu:
kbot tnt pack ./metal_heck --target metal_heck.tnt
Important
Standardmäßig ist das Entpacken verlustbehaftet. Die Namenstabelle
der Kartenobjekte fehlt in metadata.json und wird beim Packen aus den
eindeutigen Namen in features.csv neu aufgebaut. Dabei gehen alle
Speicherreste verloren, die Cavedogs Werkzeuge hinten in der Tabelle
hinterlassen haben. Für byte-identische Round-Trips übergib --lossless,
um die rohen Objektbytes festzuhalten:
kbot tnt unpack "metal heck.tnt" -t ./metal_heck --lossless
Zugehörige .ota-Dateien
Zu jeder .tnt gehört eine .ota-Begleitdatei („Online Total
Annihilation“) im selben Verzeichnis. Dort stehen die Missions-Metadaten:
lesbarer Name, Planetenname, Wetter, Schwerkraft, Startpositionen,
KI-Briefing usw.
Siehe TDF – .ota hat dasselbe INI-ähnliche Textformat wie
.fbi und .tdf. kbot tnt preview liest sie automatisch, um die
nummerierten Markierungen der Startpositionen in das Vorschaubild zu
zeichnen. Jede Ansicht einer Karte in kbot liest die Schemata und
Startpositionen der .ota so, wie das Spiel es tut (siehe
TDF – OTA); der Karteneditor des Studios
bearbeitet die .ota einer geöffneten Karte beim Speichern direkt.
Stolperfallen
Warning
Der Karteneditor (TAE) schreibt in manchen Versionen
Height = actual_height + 6 – Sarumans Dokumentation von 1997 merkte
dazu an: „weird, 6 more than used“. Moderne Werkzeuge behandeln die Maße
im Header als maßgeblich und ignorieren die unbenutzten Zeilen am Ende.
Der Leser von kbot verhält sich genauso.
SeaLevel == 0bedeutet NICHT „kein Wasser“ – es bedeutet, dass Höhenbyte 0 die höchste Unterwasserhöhe ist, also nichts unter Wasser liegt. Karten ohne Wasser verwenden typischerweiseSeaLevel = 1.- Kachelindizes sind
uint16, eine einzelne Karte kann also bis zu 65.536 einzigartige Kacheln haben – in der Praxis treibt aber alles über ein paar Tausend die Engine unter Speicherdruck ins Auslagern. Metal Heck (583 Kacheln) liegt näher am Median als am Rand der Verteilung. Das Studio weigert sich, eine Karte zu speichern, die mehr braucht, undkbot tnt packweist Werte intilemap.csvab, die keine Indizes der Kacheln der Karte sind (etwa-1oder70000, das zu 4.464 überlaufen würde). Eine Zelle, die eine Kachel jenseits des Kachelsatzes nennt, lädt (das Spiel liest über seinen Kachelsatz hinaus);kbot tnt lintmeldet sie alsbad-tile-index. - Die Auffüllung der Minikarte ist Palettenindex
0x64; die Engine ignoriert Pixel außerhalb des Kartenbereichs. - Bit 0 von
MinimapFlags(0x2c, bzw.0x3cbei einer0x1020-Karte) muss gesetzt sein, sonst ignoriert das Spiel die gespeicherte Minikarte. Lass die übrigen Bits, wie sie sind. - Die vier
pad-Felder sind immer null. Manche Editoren von Drittanbietern schreiben Werte ungleich null hinein; das Spiel scheint sie zu ignorieren, aber kbot bewahrt, was es vorfindet. - TA: Kingdoms verwendet ein verwandtes, aber inkompatibles
Kartenformat (
.btm- /.bmp-Höhenkarten + Kachelseiten). Siehe TA:K-Karten.
Typische Größen
| Asset | Beobachteter Bereich in Cavedogs maps/*.tnt |
|---|---|
| Dateigröße | 0,3–7 MB |
| Kartenmaße (Kacheln) | 64×64 – 256×256 (also 2.048–8.192 px Seitenlänge) |
| Einzigartige Kacheln | 200–2.000 |
| Platzierungen von Kartenobjekten | 10–600 |
| Objektdefinitionen in der Tabelle | 5–50 |
| Header-Overhead | immer 64 Byte |
| Minikarte (252×252 mit Palette; 33 Karten der Verkaufsversion 252×256) | 63.504 Byte |
Textgröße der zugehörigen .ota |
1–8 KB |
Echte Beispiele im Referenzkatalog
Karten, die einen genaueren Blick lohnen – aus jeder TA-Installation unter
maps/<name>.tnt zu entpacken:
metal heck.tnt– das Beispiel im Detail oben. 4.192×4.192 px, 583 einzigartige Kacheln, 74 Platzierungen von Kartenobjekten. Legendäre, rohstoffreiche Karte.a gentle time.tnt– kleine Karte für 4 Spieler; ein guter Einstieg, wenn du einen Prototyp eines TNT-Betrachters oder einer Render-Pipeline baust.lava run.tnt– hat ungewöhnliche Objekt-Sentinel-Werte0xFFFEin den Attributzellen (eine der Karten, die die Randfälle des frühen Reverse Engineering geprägt haben).acid foursome.tnt– große Karte für 8 Spieler; zeigt weit verteilte Startpositionen von[Schema 0]in der zugehörigen.ota.example.tnt– die kleinste ausgelieferte TNT (~263 KB); eine minimale funktionsfähige Karte, um den Round-Trip mitkbot tnt unpack/packzu testen.
Zu den TNT-Varianten von TA: Kingdoms (anderes IDVersion-Wort) siehe die Forschungsnotizen zu TA:K-Karten.
Siehe auch
- SCT – die Bausteine, die TAE zu TNTs zusammensetzt.
- TDF – Objektdefinitionen, die über den Namen aufgelöst werden,
und das Format der zugehörigen
.ota. - PAL – die Palette, mit der Kachelpixel und Minikarte gerendert werden.
- GAF – Objekt-Sprites, die
kbot tnt previewzusammensetzt. - TA:K-Karten – die ganz andere Karten-Pipeline von TA: Kingdoms.
- Glossar – attribute cell, sea level, void cell.