------------------------------------------------------------------------------- Total Annihilation TNT Map Format 2003 by Scott 'me22' McMurray MSN/Email me22@fastmail.ca ; ICQ 37213887 ------------------------------------------------------------------------------- Big Thanks to Saruman & Bobban (DFR Engineering) for the first version of this document, as well as to Kinboat, author of the 3rd party map editor Annihilator, for most of the format and explications. The original version of this document, as well as the latest version, can be found at http://www.tauniverse.com/~visual-ta , which also has Kinboat's source code for Annihilator (in the Utilities Section). They did the hacking, all I've done is gone through and updated Saruman & Bobban's work with what Kinboat figured out but didn't write up. I have tried to fully rewrite this document to fill in the gaps and make everything more clear. However, I have kept the variables' previous names. I hope you find this useful. If so, please tell me. If you think I've missed something or made a mistake, contact me. If you just want to flame me for fnu, send it to dev/null. ------------------------------------------------------------------------------- NB: I am assuming that a char is 1 Byte, a short 2, and a long 4. Also, I'll be using C++ code throughout -- some of it may not be C-compatible. This includes the 0x prefix for hex numbers. Also, a ... referres to some code that must be included that depends on the situation but should be obvious. Also, the example code does not include the error checking which would obviously be included in any program, and never declares the variable named x used in the for(;;) loops. ------------------------------------------------------------------------------- General Notes: - Colours are always 8 bits-per-pixel indexes in the TA Palette - The top left of every image in (0,0), which is annoying for OpenGL which uses a bottom left is (0,0) system ------------------------------------------------------------------------------- First, the header structure: struct TNTheader { // Length: 0x40 // Offset: unsigned long IDVersion; // 0x00 unsigned long Width; // 0x04 unsigned long Height; // 0x08 unsigned long PtrMapData; // 0x0C unsigned long PtrMapAttr; // 0x10 unsigned long PtrTileGfx; // 0x14 unsigned long Tiles; // 0x18 unsigned long TileAnims; // 0x1C unsigned long PtrTileAnims; // 0x20 unsigned long SeaLevel; // 0x24 unsigned long PtrMiniMap; // 0x28 unsigned long unknown1; // 0x2C unsigned long pad1; // 0x30 unsigned long pad2; // 0x34 unsigned long pad3; // 0x38 unsigned long pad4; // 0x3C }; Convenient multipule-of-4 size, it can be read directly. fstream inFile; TNTheader myHeader; inFile.open( ... ); inFile.read( (char*)&myHeader, sizeof(TNTheader) ); ------------------------------------------------------------------------------- And the descriptions of the header members: IDVersion: This is a version/file type marker of some kind. It is always 0x2000 in valid TNT files. Width: Height: The width/height of the map in 16-pixel units as used for feature/height information. To get the width/height in 32-pixel units ( the size of a tile ), divide by 2. PtrMapData: Offest from the beginning of the file to the tile indicies array. This array contains unsigned shorts that are the indecies in the tile array of the 32x32 pixel tile to be displayed at that point. unsigned short *tileIndicies = new unsigned short[ myHeader.Width/2 * myHeader.Height/2 ]; inFile.seekg( myHeader.PtrMapData, ios::beg ); inFile.read( (char*)tileIndicies, sizeof(unsigned short)*(myHeader.Width/2 * myHeader.Height/2) ); PtrMapAttr: Offset from the beginning of the file to the map attributes array. This array holds the height and feature information for every 16x16 pixel square on the map. The first byte is an unsigned char that holds the height, the second and third an unsigned short holding the offset in the feature array of the feature located in that square ( or 0xFFFF if none, or 0xFFFC (-4) if void. I've also seen 0xFFFE (-2) on some of the early Cavedog maps such as Lava Run and AC02, but have no idea what it means O_o Please contact me if you have any information! ), and the last has no known purpose ( my personal guess is that it is padding to get to 4 bytes ). unsigned char *heights = new unsigned char[ myHeader.Width*myHeader.Height ]; unsigned char *featureIndicies = new unsigned short[ myHeader.Width*myHeader.Height ]; inFile.seekg( myHeader.PtrMapAttr, ios::beg ); for ( x = 0; x < myHeader.Width*myHeader.Height; x++ ) { heights[x] = inFile.get(); inFile.read( (char*)&featureIndicies[x], sizeof(unsigned short) ); inFile.seekg( 1, ios::cur ); // Jump Over Padding } NB: There are 4 of these squares for each square in the tile indicies array. PtrTileGfx: Tiles: This is the array of tiles referenced by the tileIndecies array. One tile consists of unsigned char[32*32]. typedef unsigned char[32*32] TNTtile; TNTtile *tiles = new TNTtile[ myHeader.Tiles ]; inFile.seekg( myHeader.PtrTileGfx, ios::beg ); inFile.read( (char*)tiles, sizeof(TNTtile)*myHeader.Tiles ); TileAnims: PtrTileAnims: This is the array of features referenced by the featureIndicies array. Each entry is 0x84 bytes long and consists of an unsigned long that always seems to equal the index of the feature in this array and a 128-character null-terminated string the contains the name of the feature to be placed in the location as found between the square brackets ( [] ) in a TDF feature file. struct TNTfeature { char name[128]; }; TNTfeature *features = new TNTfeature[ myHeader.TileAnims ]; inFile.seekg( myHeader.PtrTileGfx, ios::beg ); for ( x = 0; x < myHeader.TileAnims; x++ ) { inFile.seekg( 4, ios::cur ); // Jump Over unsigned long Index inFile.read( (char*)features[x].name, sizeof(128) ); } NB: The index seems to be totally useless, which is why I simply skip over it. I have noticed that while Cavedog maps include them, Annihilator doesn't even bother to include them. SeaLevel: Any value in the heights array less than this value is deemed to be "under water" (IE water modifiers and effects apply). PtrMiniMap: Obviously enough, this is where the minimap image is found. At the offset, the first things you find are 2 unsigned long's: the minimap's width and height ( which always seem to be 252 ). What follows is width*height of pixel data. The difficult part about minimaps is that they are padded by 0xDD's ( the standard blue used as transparent in TA ). See the example code below for how to figure out the minimap's true size. unsigned long mmWidth; unsigned long mmHeight; unsigned char *mmData = 0; inFile.seekg( myHeader.PtrMiniMap, ios::beg ); inFile.read( (char*)&mmWidth , sizeof(unsigned long) ); inFile.read( (char*)&mmHeight, sizeof(unsigned long) ); mmData = new unsigned char[mmWidth*mmHeight]; unsigned long mmWidth_actual = 252; unsigned long mmHeight_actual = 252; if ( myHeader.Width > myHeader.Height ) { mmHeight_actual *= myHeader.Height; mmHeight_actual /= myHeader.Width; } else if ( myHeader.Width < myHeader.Height ) { mmHeight_actual *= myHeader.Width; mmHeight_actual /= myHeader.Height; } unknown1: pad1: pad2: pad3: pad4: Noone knows what or if these are used for. Maybe it has to do with multipule-of-4 or power-of-2 sizes again? ------------------------------------------------------------------------------- Saruman & Bobban's Observations: Interresting Notes ================== The massive size of the maps are due to the fact that they contain ALL the graphics needed for the terrain, with the exception of the special animated objects. This is both good and bad; Good ==== · If you have the .TNT file - you have what's required to play it. You don't need to rely on external textures. · The freedom of not having to rely on pre-made textures. Bad === · Filesize · ..impact on memory-consumption and transfer times. One "good" thing about this is that it is possible to create _HUGE_ maps that still are playable on low-memory systems. JUST DON'T USE A LOT OF DIFFERENT TILES! I'm sure this comes as a relief to all of you who haven't got 80Mb (as I do :-) I see a future full of cool addons to Total Annihilation. · Map compressors. Remove 'almost-identical' tiles to reduce map memory requirements. This is very doable and I will probably produce this utility myself if noone beats me to it (please do! :-) · Digital Elevation Map/Fractal => MAP converter. Tres cool! (You could also do a pornpicture => MAP converter.. er.. :-) · Map resizers. Should be possible, but I don't think I'm about to do one. A simple method would be to tile map-data. A better method would be to 'expand' the mapdata and fill the blanks using some sort of interpolation/neighbouring tile method. Could be an interresting programming assignment · Map Resource Tools. So that we wouldn't have to transfer tile-graphics with the maps. Release a tile-lib and 'link' it to your map before playing. TATool could easily be fitted with functions for this (extract Mapdata+MapAttr, TileGFX, PTRTileAnim - include the same to any other file - recalculate header..) · Map Generators. I _really_ hope one is included with the Official MapEditor(TM). Tweak some parameters, input random seed. Push _GENERATE_. This is a must! Post the parameters + seed value on the net and others would be able to generate the exact same map. · Mirror/Shift maps... etc.. etc... -------------------------------------------------------------------------------