Skip to content

war3map.w3e Terrain

Stijn Herfst edited this page Nov 15, 2017 · 26 revisions

This is the tileset file. It contains all the data about the tilesets of the map. Let's say the map is divided into squares called "tiles". Each tile has 4 corners. In 3D, we define surfaces using points and in this case tiles are defined by their corners. I call one tile corner a "tilepoint". So if you want a 256x256 map, you'll have 257x257 tilepoints. That's also why a tile texture is defined by each of its four tilepoints. A tile can be half dirt, one quarter grass and one quarter rock for example. The first tilepoint defined in the file stands for the lower left corner of the map when looking from the top, then it goes row by row. Tilesets are the group of textures used for the ground.

Format

Here is the file format:

char[4]		"W3E!"
int32		w3e format version [0B 00 00 00]h = version 11
char		main tileset [TS]
int32		Custom tilesets flag (1 = custom tileset, 0 = no custom tileset)
int32		Amount 'a' of ground tilesets used (Maximum is 16)
char[4][a] 	Ground tilesets IDs (tilesets table)
int32		Amount 'b' of cliff tilesets used (Maximum is 16)
char[4][b] 	Cliff tilesets IDs (cliff tilesets table)
int32		Width of the map + 1 = Mx
int32		Height of the map + 1 = My
float		Horizontap center offset of the map
float		Vertical center offset of the map

Then width * height tilepoints, each 7 bytes long:

int16		ground_height
int16		water_height + boundary_flag
4bits		flags
4bits		ground_texture
4bits		ground_variation
4bits		cliff_variation
4bits		cliff_texture
4bits		layer_height

Explanation

char[4]		"W3E!"

A constant string at the start of the war3map.w3e file used to identify it.

int32		w3e format version [0B 00 00 00]h = version 11

The version. Seems to always be 11? Needs more investigation

char		main tileset [TS]

This is the main tileset for the map. Each tileset has an .mpq in the game mpqs which contains some specific textures for this tileset like water or cliff textures. It can be one of the following:

  • A Ashenvale
  • B Barrens
  • C Felwood
  • D Dungeon
  • F Lordaeron Fall
  • G Underground
  • L Lordaeron Summer
  • N Northrend
  • Q Village Fall
  • V Village
  • W Lordaeron Winter
  • X Dalaran
  • Y Cityscape
  • Z Sunken Ruins
  • I Icecrown
  • J Dalaran Ruins
  • O Outland
  • K Black Citadel

int32		Custom tilesets flag (1 = custom tileset, 0 = no custom tileset)

Indicates whether this map uses a combination of textures from different tilesets or a predefined one from the list above.

int32		Amount 'a' of ground tilesets used (Maximum is 16)

Indicates how many ground textures there are. The maximum is 16 since a tilepoint only allocates 4 bits and thus can save only 16 different values.

char[a][4] 	Ground tilesets IDs (tilesets table)

A list of strings of size 4. Example: "Ldrt" stands for "Lordaeron Summer Dirt"
Refer to "TerrainArt\Terrain.slk" located in War3.mpq or War3x.mpq for more details.

int32		Amount 'b' of cliff tilesets used (Maximum is 16)

Indicates how many cliff textures there are. The maximum is 16 since a tilepoint only allocates 4 bits and thus can save only 16 different values.

char[4][b] 	Cliff tilesets IDs (cliff tilesets table)

A list of strings of size 4. Example: "CLdi" stands for Lordaeron Cliff Dirt
Refer to "TerrainArt\CliffTypes.slk" located in War3.mpq or War3x.mpq for more details.

The cliff tile list is actually ignored, the World Editor will simply add the cliff tiles for each tile in the ground tile list, if a cliff version of this ground tile exists.

int32		Width of the map + 1
int32		Height of the map + 1

Width and Height of the map in tiles. We add 1 because a map of 256 x 256 tiles will have 257 x 257 corners.

float		Horizontap center offset of the map
float		Vertical center offset of the map

These 2 offsets are used in the scripts files, doodads and more.
The original (0,0) coordinate is at the bottom left of the map (looking from the top) and it's easier to work with (0,0) in the middle of the map.
These offsets are:

-1*(Mx - 1) * 128 / 2 and -1 * (My - 1) * 128 / 2

where:

(Width - 1) and (Height - 1) are the width and the height of the map in tiles 128 is the size of a tile on the map
/ 2 because we don't want the length, but the middle.
-1 * because we are "translating" the center of the map, not giving it's new coordinates

Tilepoint

int16		ground_height

Minimum height (-16384)
Normal height (ground level 0)
Maximum height (+16383)

The tilepoint "final height" you see on the WE is given by:

(ground_height - 0x2000 + (layer - 2) * 0x0200) / 4

Where "0x2000" is the "ground zero" level, 2 the "layer zero" level and "0x0200" the layer height

int16		water_height + boundary_flag

The highest bit (bit 15) is used for the boundary flag 1.

0x4000 --> boundary flag 1 (shadow generated by the world editor on the edge of the map)

The tilepoint "water level" you see on the WE is given by:

(water_level - 0x2000) / 4 - 89.6

Where "0x2000" is the "ground zero" level, -89.6 is the water zero level. The water zero level is a constant value found in Water.slk * 128. So height = -0,7 --> water_zero_level = -0,7 * 128 = -89.6.

4bits		flags

Flags values (shown as big endian):

0x0010 --> ramp flag (used to set a ramp between two layers)
0x0020 --> blight flag (ground will look like Undead's ground)
0x0040 --> water flag (enable water)
0x0080 --> boundary flag 2 (used on "camera bounds" area. Usually set by the World Editor "boundary" tool.)

4bits		ground_texture

Which ground textures is used (dirt, grass, rock, etc...). This refers to one of the ground tilesets discussed earlier.

4bits		ground_variation

Which variation of the texture to use (bones, holes, etc...). This is to reduce the amount of repetition.

4bits		cliff_variation

? (Which model to use? Values seen are 0, 1, 2)

4bits		cliff_texture

Which cliff texture to use (dirt, grass, snow, etc...). While technically this should refer to one of the cliff tilesets discussed earlier the cliff tile list is actually ignored, the World Editor will simply add the cliff tiles for each tile in the ground tile list, if a cliff version of this ground tile exists.

4bits		layer_height

The layer height is changed when using cliffs.

Implementation

A reference implementation in C++ is available here and a Javascript implementation here.

Textures

Ground Textures

Ground Textures are either square or extended. Extended textures have extra variations for the "full" tile to reduce the amount of repetition when you have big patches of a single tile. Here you see a square (non extended) and square texture.
Non extended texture Extended texture
The ground_variation data in the tilepoints determines which of the variations to use. When the texture is square it will be either the top left variation or the bottom right variation. If the texture is extended then one of the variations from the extended part is chosen in the following order:
Dirt Numbered

As you may have noticed the square texture and the left part of the extended texture have all kinds of variations for different texture combinations. Based on how many tilepoints in a tile share the same texture a variation is chosen. This is done for every texture that the tilepoints of that tile have. Let's take an example where the bottom left, top left and top right are all dirt and only the bottom right is grass.
Example Tile
Now there is a nice formula to figure out which variation we need to pick. It requires that we number all the variations from 0 to 15 (just like we did with the extended variations). Then for every different texture in the tile (in this case dirt and grass) we make a bitstring (a sequence of 0's and 1's) and convert that to a number. Here is an example:
You will have to do this from the lowest ground_texture number to the highest since tiles can overlap eachother, thus we do dirt first.

1   1  
1   0  

We then place the top row in front of the bottom row which produces:

1 1 1 0  

Which if we convert it from binary to a number gives us 14 which is the following variation:
Dirt Variation 14
Now we repeat this for the grass texture:

0   0
0   1

Place the top row in front of the bottom row.

0 0 0 1  

Which converted from binary is the number 1 which is the following variation:
Grass Variation 1
And finally if we combine the two we get:
Combined Tile

This will work for any combination of texture tiles. Now in the example we calculated what dirt should be, but in reality the lowest ground_texture number is always the full tile since it always gets placed on the bottom. Here is an example implementation bit in C++.

std::vector<std::tuple<int, int>> tileVariations;
std::set<Corner> set({ topL, topR, bottomL, bottomR });

Corner first = *set.begin();
tileVariations.push_back({ get_tile_variation(first), first.ground_texture });
set.erase(set.begin());

std::bitset<4> index;
for (auto&& corner : set) {
	int texture = corner.ground_texture;
	index[0] = bottomR.ground_texture == texture;
	index[1] = bottomL.ground_texture == texture;
	index[2] = topR.ground_texture == texture;
	index[3] = topL.ground_texture == texture;

	tileVariations.push_back({ index.to_ulong(), texture });
}

What the code does is first get all the unique textures used by the four corners and sort them from lowest to highest (this is what the std::set does automatically). Then we take out the smallest ground_texture number and we add this to our vector (basically an array/list). After that we repeat the aforementioned process for every remaining texture.

Note: In C++ true is implicitly converted to 1 and false to 0

Clone this wiki locally