Map
This file is mirrored from the libtw2 documentation and is dual-licensed under MIT or APACHE.Introduction
Teeworlds and DDNet maps get saved as datafiles. If you are not yet familiar with parsing datafiles, please go through the datafile documentation first.
Terminology
integer types are usually declared in the same notation as in rust. Examples:
i32
is a signed 32 bit integeru8
is a unsigned 8 bit integer,byte
is used synonymously
&
is the prefix for data item indices. Those point to a data item in thedatafile.data
section of the datafile.opt
is the prefix for optional indices. They are either a normal index or-1
, marking it as unused.*
is the prefix for indices that point to another item in the datafile.- For example
*image
means that the field points to an image item.*color_envelope
would mean that the field points to the envelope item with that index, which should be a color envelope.
- For example
CString is a null terminated UTF-8 string.
I32String is a CString stored in consecutive i32 values. To extract the string:
- convert the i32s to their be (big endian) byte representation, join the bytes so that we have a single array of bytes
- the last byte is a null byte, ignore that one for now
- wrapping-subtract 128 from the remaining bytes
- now you got a CString padded with zeroes
Point is a struct with two i32s, one for x, one for y. It is usually used to describe a position in the map. 0, 0 is the top-left corner.
Color is a struct with the 4 u8 values (in order): r, g, b, a. Its still usually parsed from 4 i32s, meaning each one should hold a value that fits into an u8.
the
item_data
of an item is an array of i32s. We will split theitem_data
up into its different elements, which differ for each item type. Examples for theitem_data
syntax:[2] point: Point
=> The next two i32 values represent the variablepoint
(which will be explained afterwards) which is of the typePoint
.[1] opt &name: CString
=> The next i32 representsname
and is an optional data item index to a CString.
Item Type Overview
General map structure:
> Info
> Images
> Envelopes
> Envelope Points
> Groups
> Layers
> Auto Mappers (DDNet only)
> Sounds (DDNet only)
Maps consist of various elements that each have a type_id
that identifies them.
type_id mappings:
0 -> Version
1 -> Info
2 -> Images
3 -> Envelopes
4 -> Groups
5 -> Layers
6 -> Envelope Points
7 -> Sounds (DDNet only)
0xffff -> UUID Index (see below, DDNet only)
Use them to figure out which purpose each of the item types in the datafile.item_types
section of the datafile has.
Things to keep in mind:
- When an item type appears in
datafile.item_types
, it means that there must be at least one item of that type - With the exception of the UUID Index, the first item of an item type will have
id
= 0 and from there it will count up
UUID item types
In DDNet, some item types won't be assigned a type_id, but instead an uuid.
To find the correct item type (in datafile.item_types
for uuid item types, you will need their type_id
. You will need to figure out the type_id
manually by looking into the UUID Index items.
UUID Index Item structure:
type_id: 0xffff
id: type_id of the uuid item type that this item represents
item_data:
[3] UUID of the uuid item type that this item represents
- the twelve bytes of the uuid are laid out in order in the
item_data
when viewing the integers as big endian - steps to find an uuid item type:
- get the UUID item type
- scan through its items
- when an item has the correct uuid in its
item_data
, copy thetype_id
from theid
field - find the item type with the
type_id
that we just found out
Map Item Types
Version
type_id
= 0- exactly one item
item_data of the only version item:
[1] version
version
= 1- no actual data is stored using the Version item type
Info
type_id
= 1- exactly one item
item_data of the only version item:
[1] (item) version
[1] opt &author: CString
[1] opt &version: CString
[1] opt &credits: CString
[1] opt &license: CString
[1] opt &settings: [CString] (DDNet only)
- both vanilla and DDNet are at
version
= 1 - like indicated, all the other fields are optional data item indices
- the data item behind
settings
is an array of CStrings, all consecutive, split by their null bytes (with a null byte at the very end) - maximum amount of bytes for each CString (including the null byte):
- author: 32
- version: 16
- credits: 128
- license: 32
Images
type_id
= 2
item_data of image items:
[1] version
[1] width
[1] height
[1] external: bool
[1] &name: CString
[1] opt &data: [Pixel]
version 2 extension (Vanilla only):
[1] variant
- Vanilla is at
version
= 2, DDNet is atversion
= 1 width
andheight
specify the dimensions of the image- if
version
= 1, the image is of type RGBA, forversion
= 2variant
holds the type:- 0 -> RGB
- 1 -> RGBA
- Images can either be embedded or external.
- Embedded images have
external
= false and have the image data stored in the data field. The image data is simply a 2d-array of pixels in row-major ordering. RGBA pixels are 4 bytes each, RGB pixels 3 bytes each. - External images have
external
= true and thedata
field on-1
. Those images can only be loaded by clients that have those in theirmapres
directory, meaning only a small set of images should be external. The client looks for those images by using thename
field.
- Embedded images have
- the CString behind
name
must fit into 128 bytes - External images for both 0.6 and 0.7 (note that they might differ between versions!):
bg_cloud1
,bg_cloud2
,bg_cloud3
,desert_doodads
,desert_main
,desert_mountains2
,desert_mountains
,desert_sun
,generic_deathtiles
,generic_unhookable
,grass_doodads
,grass_main
,jungle_background
,jungle_deathtiles
,jungle_doodads
,jungle_main
,jungle_midground
,jungle_unhookables
,moon
,mountains
,snow
,stars
,sun
,winter_doodads
,winter_main
,winter_mountains2
,winter_mountains3
,winter_mountains
- Further external images for 0.7 maps:
easter
,generic_lamps
,generic_shadows
,light
Envelopes
type_id
= 3
item_data of envelope items:
[1] version
[1] channels
[1] start_point
[1] num_points
extension without version change:
[8] name: I32String
version 2 extension:
[1] synchronized: bool
- DDNet is at
version
= 2, Vanilla chooses 3 for all envelopes when one of them uses a bezier curve, but falls back to 2 when there is none. channel
holds the type of the envelope- 1 -> Sound envelope
- 3 -> Position envelope
- 4 -> Color envelope
synchronized
has the effect that the envelope syncs to server time, not player join timestart_point
is the index of its first envelope pointnum_points
is the number of envelope points for this envelope
See Envelope Points to see how the envelope points are stored.
Envelope Points
type_id
= 6- exactly one item
The item_data
of the only item contains all the envelope points used for the envelopes.
- Size of each envelope point:
- 22 i32s, if all envelopes have
version
= 3 - 6 i32s, if all envelopes have a
version
<= 2
- 22 i32s, if all envelopes have
- Note that all unused fields are zeroed
The first 6 i32 of each envelope point, depending on the envelope type it belongs to:
sound envelope point:
[1] time
[1] curve type
[1] volume
[3] -
position envelope point:
[1] time
[1] curve_type
[1] x
[1] y
[1] rotation
[1] -
color envelope point:
[1] time
[1] curve type
[4] color: I32Color
time
is the timestamp of the point, it should increase monotonously within each envelopecurve_type
holds how the curve should bend between this point and the next one- 0 -> Step (abrupt drop at second value)
- 1 -> Linear (linear value change)
- 2 -> Slow (first slow, later much faster value change)
- 3 -> Fast (first fast, later much slower value change)
- 4 -> Smooth (slow, faster, then once more slow value change)
- 5 -> Bezier (Vanilla only, very customizable curve)
x
andy
hold the movementI32Color actually means that the color values for r, g, b, a are i32 values
If bezier curves are used anywhere (envelope version 3), then there are 16 more i32 for each point. These are only non-zero if the curve_type
of the point is 5 (Bezier):
bezier point extension:
[4] in_tangent_dx
[4] in_tangent_dy
[4] out_tangent_dx
[4] out_tangent_dy
Groups
type_id
= 4
item_data of group items
[1] version
[1] x_offset
[1] y_offset
[1] x_parallax
[1] y_parallax
[1] start_layer
[1] num_layers
version 2 extension:
[1] clipping: bool
[1] clip_x
[1] clip_y
[1] clip_width
[1] clip_height
version 3 extension:
[3] name: I32String
- both Vanilla and DDNet are at
version
= 3 start_layer
andnum_layers
tell you which layers belong to this group. Groups are not allowed to overlap, however, the reference implementation has no such checks while loading.- the 'Game' group, which is the only one that is allowed to hold physics layers, should have every field zeroed, only
x_parallax
andy_parallax
should each be 100 and thename
should be "Game". Note that the reference implementation does not verify this but instead just overwrites those values - all maps must have a 'Game' group, since every map must have a 'Game' layer which can only be in the 'Game' group
Layers
type_id
= 5
Layer types:
- Tilemap layers:
- Tiles layer
- Physics layers:
- Game layer
- Front layer (DDNet only)
- Tele layer (DDNet only)
- Speedup layer (DDNet only)
- Switch layer (DDNet only)
- Tune layer (DDNet only)
- Quads layer
- Sounds layer (DDNet only)
- Deprecated Sounds layer (DDNet only, replaced by Sounds layer)
Note that:
- All physics layers should be unique, but this isn't properly enforced on all DDNet maps. The reference implementation uses the last physics layer of each type.
- All maps must have a Game layer
item_data base for all layer items (different types have different extensions):
[1] _version (not used, was uninitialized)
[1] type
[1] flags
flags
currently only has the detail flag (at 2^0), which is used in Quad-, Tile- and Sound layers.type
holds the type of layer:- 2 -> Tilemap layer
- 3 -> Quads layer
- 9 -> Deprecated Sounds layer
- 10 -> Sounds layer
item_data extension for tilemap layers:
[1] version
[1] width
[1] height
[1] flags
[4] color: Color
[1] opt *color_envelope
[1] color_envelope_offset
[1] opt *image
[1] &data: 2d-array of the the tile type 'Tile'
version 3 extension:
[3] name: I32String
DDNet extension (no version change):
[1] opt &data_tele
[1] opt &data_speedup
[1] opt &data_front
[1] opt &data_switch
[1] opt &data_tune
Vanilla is at
version
= 4, DDNet atversion
= 3width
andheight
specify the dimensions of the layerflags
tells you what kind of tilemap layer this is:- 0 -> Tiles
- 1 -> Game
- 2 -> Tele
- 4 -> Speedup
- 8 -> Front
- 16 -> Switch
- 32 -> Tune
color
,color_envelope
,color_envelope_offset
,image
are only used by the tiles layerall tile types consist of bytes (u8)
all 2d-arrays of tiles use row-major ordering
all tile types have the
id
byte, which identifies its use- for example in the game layer, 0 is air, 1 is hookable, etc.
many have a
flags
byte, which is a bitflag with the following bits:- 2^0 -> vertical flip
- 2^1 -> horizontal flip
- 2^2 -> opaque
- 2^3 -> 90° rotation
- order of flips and rotations: vertical flip -> horizontal flip -> rotation
'Tile' tile type (consisting of bytes, used by all vanilla layers and the front layer):
[1] id
[1] flags
[1] skip
[1] - unused
- the
skip
byte is used for the 0.7 compression, which is used ifversion
>= 4:- the
data
field no longer points to an 2d-array of tiles, but instead to an array of 'Tile' tiles which must be expanded into the 2d-array - the
skip
field of each tile in the array tells you how many times this tile is used in a row. For example:- 0 means that it appears only once there
- 3 means that you need to add 3 more copies of that tile after this one
- note that the maximum value for
skip
is 255 - set the
skip
field to 0 while expanding- Teeworlds rendering assumes that those values are set to 0
- saving the tiles with 0.7 compression is less tedious, since you can check for equality of the entire tile struct
- saving the tiles again without the compression is less error prone. Since any saved map could be fed back into Teeworlds rendering, you need to set the
skip
values to 0 in this step
- the
DDNet only content:
- each physics layer uses a different data field pointer, keep in mind to use the correct one, when saving maps, set the unused pointers to -1
- the DDNet extension came before the
version
= 3 extension, meaning you have to subtract 3 (the length of thename
field) from the data index - you might have noticed that the
data
field is not actually optional like all the other data fields. For vanilla compatibility, thedata
field always points to a 2d-array of tiles of the type 'Tile', with the same dimensions as the actual layer, but everything zeroed out
Special tile types:
'Tele' tile type (consisting of bytes):
[1] number
[1] id
number
is the number of the teleporter exit/entry to group them together
'Speedup' tile type (consisting of bytes):
[1] force
[1] max_speed
[1] id
[1] - unused padding byte
[2] angle: i16
- angle is LE
'Switch' tile type (consisting of bytes):
[1] number
[1] id
[1] flags
[1] delay
number
once again tells you which tiles interact with each other
'Tune' tile type (consisting of bytes):
[1] number
[1] id
number
stores which zone this is, zones are defined in the map info -> settings
Quads layer
item_data extension for quads layers:
[1] version
[1] num_quads
[1] &data: [Quads]
[1] opt *image
version 2 extension:
[3] name: I32String
- both Vanilla and DDNet are at
version
= 2 num_quads
is the amount of quads found behind the data item pointerdata
- the size of a quad in bytes is 152, however we will pretend that the data consists of i32 when looking at the Quad structure:
Quad:
[8] positions: [Point; 5]
[16] corner_colors: [Color; 4]
[8] texture_coordinates: [Point; 4]
[1] opt *position_envelope
[1] position_envelope_offset
[1] opt *color_envelope
[1] color_envelope_offset
positions
elements 1 - 4 are the corner positions andpositions
element 5 contains the pivot- to map the
positions
to world coordinates divide them by 512 - corners are in the order top-left -> top-right -> bottom-left -> bottom-right
- the
texture_coordinates
are in range (0, 1024). To get the actual texture coordinates, divide by 1024 to normalize to (0, 1) range and multiply by the dimension of the quad image.
Sounds layer
item_data extension for sounds layers:
[1] version
[1] num_sources
[1] &data: [SoundSource]
[1] opt *sound
[3] name: I32String
- num_sources is the amount of sources behind the data item pointer
data
- the size of a sound source in bytes is 52, however we will pretend that the data consists of i32 when looking at the SoundSource structure:
- the CString behind
name
must fit into 128 bytes
SoundSource:
[2] position: Point
[1] looping: bool
[1] panning: bool
[1] delay (in seconds)
[1] falloff: u8
[1] *position_envelope
[1] position_envelope_offset
[1] *sound_envelope
[1] sound_envelope_offset
[3] shape: SoundShape
SoundShape:
[1] kind
[1] width / radius
[1] height / - unused
kind
:- 0 -> rectangle (use
width
andheight
) - 1 -> circle (use
radius
)
- 0 -> rectangle (use
Deprecated Sounds layer
- the
item_data
is the same as in the Sounds layer - difference is the SoundSource struct, which here only uses 36 bytes:
deprecated SoundSource:
[2] position: Point
[1] looping: bool
[1] delay
[1] radius
[1] *position_envelope
[1] position_envelope_offset
[1] *sound_envelope
[1] sound_envelope_offset
Use the following values to convert a deprecated SoundSource:
panning
= truefalloff
= 0shape
: kind = circle, with sharedradius
Sounds
type_id
= 7- DDNet only
item_data of sound items:
[1] version
[1] external: bool
[1] &name: CString
[1] &data
[1] data_size
- DDNet is at
version
= 1 - in theory, sounds can be external like images. However, since there are no sounds that could currently be loaded externally, this feature has been removed. This means that
external
should always be false anddata
should not be considered an option index - the data item index
data
points to opus sound data
Auto Mappers
uuid
=16271b3e-8c17-7839-9bd9-b11ae041d0d8
- DDNet only
item_data of auto mapper items:
[1] _version (not used, was uninitialized)
[1] *group
[1] *layer
[1] opt config
[1] seed
[1] flags
group
points to a group,layer
is the layer index within the groupflags
currently only has theautomatic
flag at 2^0, which tells the client to auto map after any changes- while only Tiles layer use auto mappers, physics layers may also have one. When saving, only save auto mappers for Tiles layers