ELVL Format

From ASSS Wiki
Jump to: navigation, search

ELVL Format now superecedes the RGN Format.

The latest version can always be found at in the monotone repository.

extended level file format
version 0.4
grelminar@yahoo.com
------------------------

0. note

this document is a work in progress. it often speaks about things being
implemented in asss. it's often wrong: implementation of the features
described in this document is still ongoing, and many features described
here are not yet implemented.


1. background

the basic level format, in use for many years, supports two main pieces
of data: a bitmapped tileset for the map, and the tile data. the tileset
bitmap is optional. the file layout is relatively simple: the bitmap
comes first, if it's present, followed by tile data to the end of the
file. if the bitmap signature isn't present, applications assume the
whole file contains tile data.

to support advanced features that are already, or will soon be, possible
in asss, i'd like to propose a backwards-compatible extension of the
basic level format.


2. goals

the primary goal is to make it possible to embed more metadata in level
files. this includes descriptive information about the creator of the
file and its intended use, but also some functional information.

the most important, and complicated, type of metadata that i'd like to
embed are regions. a region is just a set of tiles, usually but not
necessarily contiguous, plus some information describing how the server
(and maybe eventually the client) should treat that set of tiles.


3. file format

3.1. embedding metadata in lvl files

currently, level files have one of two formats:

    <bitmapfileheader>
    <bitmapinfoheader>
    <color table>
    <bitmap data>
    <tile data>

or just:

    <tile data>

for now, i'm just going to talk about level files that contain tilesets.
tileset-less files will be discussed later.

to preserve backwards compatibility with all the programs that read or
write level files (subspace, continuum, ssme, ssled, facts, subgame,
etc.), the beginning of the file will have to remain the same. the tile
data will also have to remain the last item in the file, since all the
level-file-reading code assumes that all the data from a specific point
to the end of the file is tile data. thus, the new data will have to be
inserted in the middle of the file, making it look like this:

    <bitmapfileheader>
    <bitmapinfoheader>
    <color table>
    <bitmap data>
    <metadata/regions>    <-- new stuff
    <tile data>

the trick that makes this work is the fact that the bitmapfileheader
contains a field that specifies the total file size, which programs use
to locate the start of the tile data. (at least i hope they do. if any
program assumes the tile data directly follows the bitmap data, these
extended files will break it.) another nice feature is that header also
contains 4 bytes of reserved space, which we will use to point to the
metadata section.

for reference, the windows BITMAPFILEHEADER struct looks like this:

struct BITMAPFILEHEADER {
    u16 bfType;         // == 0x4D42 ("BM")
    u32 bfSize;         // the total size of the file, which is also
                        // the offset of the tile data
    u16 bfReserved1;    // previously reserved, but we're going to
                        // use this for our format
    u16 bfReserved2;    // this one is still unused, should be 0
    u32 bfOffBits;      // the offset to the bitmap data
};

so, to make an extended level file, lay out your two bitmap headers,
color table, and bitmap data as before, then the metadata section, and
finally the tile data. the metadata section should be aligned to start
at a multiple of 4 bytes from the start of the file, so you might need
0-3 bytes of padding between the end of the bitmap data and the start of
the metadata. the metadata section will always be a multiple of 4 bytes
long, so the tile data will also always start on a multiple of 4 bytes.

set the bfReserved1 field to the byte position of the start of the
metadata section. typically, this will be 49720 bytes. also, the bfSize
value, which was previously almost always 49718, will now point to the
tile data, which will be located right after the extended metadata. so
for a file containing 4000 bytes of metadata, bfSize will be 53720.

note that this means from the perspective of a program that only
understands bmp files, or one that only understands plain lvl files, the
metadata is part of the bitmap. it just happens that the bitmap contains
more bits than is necessary for its dimensions and color depth.

to make an extended level file without a tileset, you simply start off
the file with the metadata chunk, followed by the tile data. WARNING:
currently no programs except asss understand extended level files
without tilesets.


3.2. metadata format

now, the format of the metadata section itself. this is designed to be
extensible, so new features can get added to lvl files without
redefining the whole format. it's somewhat inspired by the png file
format, which was inspired by iff, etc.

first, there's a header:

struct metadata_header {
    u32 magic;          // == 0x6c766c65 ("elvl", for "extended lvl")
    u32 totalsize;      // the number of bytes in the whole metadata section
    u32 reserved;       // reserved, should be 0
};

magic should always be equal to 0x6c766c65. if it isn't, the file is
incorrect or corrupted. incidentally, the magic value is how extended
level files without tilesets can be identified. (that is, an initial
"BM" means an initial tileset, which may or may not contain extended
data, an initial "elvl" means an extended level file without a tileset,
and anything else is a plain level file without a tileset.) also note
that the magic value is an illegal value for tile data.

totalsize is the number of bytes in the whole metadata section,
including the header. note that the tile data will start at an offset of
totalsize bytes from the start of the metadata_header.

directly following the header is a series of "chunks". there is no limit
to the number of chunks. each chunk contains a chunk header:

struct chunk_header {
    u32 type;   // describes what this chunk represents
    u32 size;   // the number of bytes in the data portion of this
                // chunk, _not_ including the header.
};

the header is followed by arbitrary data whose interpretation is
determined by the chunk type. the data portion of a chunk may be of any
length, including 0 bytes and odd numbers of bytes. if a chunk isn't a
multiple of 4 bytes long, it should be padded up to a multiple of 4
bytes so that the next chunk starts on a 4-byte boundary (but the
padding isn't counted in the size field, it's just understood to be
there).

an application can ignore chunk types that it doesn't know about. if
it's an editor that's loading and saving elvl files, it should probably
save the extra chunks and write them back to the file when it gets
saved.

there are several chunk types defined so far:

"ATTR" - defines a textual attribute
"REGN" - defines a region
"TSET" - defines a tileset
"TILE" - defines tile data

i'll describe their function and format one by one:

3.2.1. "ATTR" chunks

these define miscelaneous textual attributes. the format is ascii text,
not null terminated, in this form: "<key>=<value>". each "ATTR" chunk
should contain just one key/value pair. multiple chunks of this type may
be present in one file.

some keys that might be typically found in a level file are:

"NAME" - a descriptive name for this map
"VERSION" - a version number for this map
"ZONE" - the zone this file is intended to be used with
"MAPCREATOR" - the person who created this map (the format of the value
               should be "name <email>")
"TILESETCREATOR" - the person who created the tileset
"PROGRAM" - the name of the program that was used to create this level
            file

3.2.2. "REGN" chunks

these chunks define regions. to recap, a region is a set of tiles,
usually but not always contiguous, with certain properties. asss
understands regions and can implement some advanced features using them.
currently continuum doesn't understand regions, but it would be nice if
it did, and we'll be able to do even cooler things when it does.

there's a lot of stuff that you might want to say about a region, so to
support all the varied uses, and also future uses, we'll use the chunk
model again: each region gets its own set of "subchunks" describing its
function. to avoid confusion, all sub-chunk types that go inside the
"REGN" superchunk start with "r". the data of the big "REGN" chunk is
simply a series of subchunks.

here are some defined sub-chunk types:

"rNAM" - a descriptive name for the region
"rTIL" - tile data, the definition of the region
"rBSE" - whether the region represents a base in a flag game
"rNAW" - no antiwarp
"rNWP" - no weapons
"rNFL" - no flag drops
"rAWP" - auto-warp
"rPYC" - code to be executed when a player enters or leaves this region

3.2.2.1. "rNAM" chunks

this is just a plain ascii string, not null terminated. every chunk
should have exactly one of these.

3.2.2.2. "rTIL" chunks

this subchunk describes the tiles that make up the region. it's stored
in a compact rle-ish representation.

conceptually, a region is some subset of the 1024x1024 tiles in the map.
to encode a region, encode it row by row, left to right, top to bottom.

for each row, split it into runs of empty tiles and present tiles. for
each run, output one of these bit sequences:

    000nnnnn          - n+1 (1-32) empty tiles in a row
    001000nn nnnnnnnn - n+1 (1-1024) empty tiles in a row
    010nnnnn          - n+1 (1-32) present tiles in a row
    011000nn nnnnnnnn - n+1 (1-1024) present tiles in a row

for the two-byte sequences, the bits in the second byte are the low
bits, and the two bits at the end of the first byte are the high bits.

for example, if you have a run of 10 present tiles, you'd output the
byte 01001001, or 73 decimal. if you have a run of 300 empty tiles,
you'd output 00100001 00101011, or 33 43.

for each row, you must output a sequence of runs that sum to exactly
1024 tiles. anything else (missing tiles at the end of a row, or a run
that extends past the end of the line) is an error.

there are two exceptions to the above rule: first, rows that contain no
tiles at all can (optionally) be encoded specially:

    100nnnnn          - n+1 (1-32) rows of all empty
    101000nn nnnnnnnn - n+1 (1-1024) rows of all empty

second, if the same pattern of tiles appears in more than one
consecutive row, you can use these special codes to save more space:

    110nnnnn          - repeat last row n+1 (1-32) times
    111000nn nnnnnnnn - repeat last row n+1 (1-1024) times

the rTIL chunk must describe exactly 1024 rows. this might require only
two bytes (163 255 for the totally empty region), or it might require
much more.

note: several bit patterns don't correspond to any of the above codings.
that's deliberate. don't use them.

3.2.2.3. "rBSE" chunks

this is a 0-byte chunk. its presence signifies that this region should
be considered a "base".

3.2.2.4. "rNAW" chunks

this is a 0-byte chunk. if present, antiwarp should be disabled for
ships in this region. currently, this disabling happens on the server,
and players whose antiwarp is being disabled aren't necessarily aware of
it. it would be nice if in the future continuum understood this feature
so that it could inform the player that antiwarp is unavailable in this
location.

3.2.2.5. "rNWP" chunks

this is a 0-byte chunk. if present, all weapons are non-functional in
this region. the same notes apply to this feature as to the no antiwarp
feature.

3.2.2.6. "rNFL" chunks

this is a 0-byte chunk. if present, any flags dropped in this region are
respawned as neutral flags in the center of the map (or wherever the
settings indicate they should be spawned).

3.2.2.7. "rAWP" chunks

this chunk, if present, turns on the auto-warping feature. any player
entering this region will be immediately warped to the specified
destination.

the data for this chunk specifies the destination, in this format:

struct autowarp_destination {
    i16 x;           // the destination x coordinate, 1-1023, 0 for the
                     // default, or -1 for the player's current x coordinate.
    i16 y;           // the destination y coordinate.
    char arena[16];  // the destination arena name. null if the warp
                     // does not cross arenas.
};

as a simple space optimization, if the warp does not cross arenas, you
can leave out the 16 bytes of arena name, so that the whole chunk data
is 4 bytes long.

3.2.2.8. "rPYC" chunks

this chunk lets you embed code in level files. the exact semantics of
this data haven't yet been determined, but it'll probably go something
like this:

this chunk should contain ascii data representing some python code. the
code will be executed when the map is loaded, and it may define several
functions: a function named "enter", if it exists, will be call each
time a player enters this region. it will be called with one argument,
which is the player who entered the region. a function named "exit"
works similarly, except of course it gets called when someone leaves.


3.2.3. "TSET" and "TILE" chunks

these chunk types are intented to be used only in the far future, when
all lvl files use the extended format, and the backwards-compatible
format described so far doesn't have to be used. such files will start
with the "elvl" metadata header described above, and they will contain
the tileset bitmap and tile data as chunks.

the format of a "TSET" chunk is a windows format bitmap, _without_ the
bitmapfileheader stuff (because its function is taken care of by the
chunk header). that is, it starts with a bitmapinfoheader, which is
followed directly by the color table, which is followed directly by the
bitmap data. the bitmap data should be in 8-bit format with no
compression.

the format of a "TILE" chunk is just tile data, in the same format it's
always been in:

struct tile {
    unsigned x : 12;   // range 0-1023, upper two bits should be 0
    unsigned y : 12;
    unsigned tile : 8;
};

there should be no more than one of each of these chunks in a file. one
file should not contain both an ETST and a TSET chunk. if a file
contains neither of those two types, the default tileset should be used.


4. comments

if you have an suggestions for improving the file format or this
document, email them to grelminar@yahoo.com, or post them on the forums
at http://forums.minegoboom.com/


5. history

version 0.3:
  - removed ETST chunks, since cont basically supports this
    functionality already through tiles.bm2

version 0.2:
  - better format for region tile data

version 0.1:
  - initial version