Friday, 19 August 2016

Even More I76 - Levels and Heightmaps

Some level terrain looks suspiciously hand-drawn

Before I forget though... the .map files

I didn't mention the .map files in the last post, because they're pretty simple - they are just direct bitmap files, used for some of the surface and lower resolution textures. Each file opens with a 32 bit pair of integers for width & height, and then it's just a 1 byte/pixel lookup into the game CLUT.

zteam.map contains a team photo easter egg

Onto levels

The level layout is defined by the mission file and a terrain map. These are held under the “missions” directory on the install CD and in the target install directory. Fortunately between the level editor, and similarities to the (already documented) Battlezone formats, we can decipher quite a bit of the detail.

The terrain map is a fairly simple format – it's held in a ".ter" file as packed set of 128 x 128 x 16 bit terrain values. So each terrain block is 32KBytes.

Each 16 bit element represents a height and some terrain flags which determine some rendering decoration details and rolling resistance. For our current purposes of this we're just interested in the height information, which is the lower 12 bits of the value, giving us a height from 0 – 4095.

Each 16 bit value represents a 5 meter section in the game, and so each terrain block of 128*128 values is a 640 x 640 meter region.

So parsing this file gives us a number of square blocks of terrain, however to know how to put these together to make a level layout we need to look into the mission file.

This is a “BWD2” file format with the extension “.msn” (for missions supplied with the game, “.lvl” for editor created ones). This contains a number of nested Tag/length/data fields. However to extract the layout we need to look at the zone map, which is prefixed by a “ZMAP” tag.

The zone map is an 80 x 80 grid of bytes, each of which references a terrain block. The value of 0xff is used to indicate a “default” zone – this is a flat piece of the map with the default texture for the level.

Non-default values represent a reference to one of the blocks of terrain, and are simply an index (so the value '0' represents the first block, '1' the second, etc). It's unusual to use more than a handful of terrain blocks in a map – the biggest is the second trip level, which uses 59 terrain segments.

The first byte of the ZMAP data is actually a count of the number of terrain zones that this zonemap references, so the ZMAP data is:
  • 4 byte ZMAP label
  • 4 byte length (always 6409)
  • 1 byte terrain reference total count
  • 6400 bytes, representing the 80 x 80 zone map.

(In theory as a result of all this the largest possible level could run a straight line section of up to 80 x 640  meters, which is 51.2 kilometers, or about 32 miles a side.)

The terrain file to use with a particular map is held in the “ZONE” tagged data field in the level description, but it's usually generated with a matching name to the mission file, e.g. "t01.msn" uses "t01.ter".

Ensuring the orientation is correct is a bit fiddly – I fill in the height maps in pixmap order, and rotate them when assembling the complete map. Also the zone information runs with 0,0 in the bottom left hand corner rather than top right, requiring some final image map rotation to get the orientation "right".

However putting these together, and just using the 12 bit height field values then for something like the melee "crater" map this gets us:

Although we would have to extract more information to determine the road layout (e.g. the RDEF field) we can actually see most of the roads based on the additional information in the terrain map – the roads have different higher order flag bits set to the default terrain.

The bowl is pretty simple, with the dirt road running around the top edge:


And the first TRIP level looks like this: