To create a recording of your Quake II play start any map and then use the console command record name. This records the game play from your point of view into the file demos/name.dm2. demos is a sub-directory of the current game directory. To stop this recording use stop or even quit the whole game (quit). To play it back, use the command demomap name.dm2 or simply map name.dm2. This kind of recording is called ``client side recording''.
To create a ``server side recording'' invoke at the server during a running game the console command serverrecord name. This records all information of all entities in the whole level but no player information into the file demos/name.dm2. Quake II can't play back these server side recordings.
The third variant of DM2 files should be better called RLA files. They are created by the Quake II Relay server modification. This is a user-made modification to create a special kind of server side demos but they can be read in again (at the server side) and replayed by any client, who connects to this modified server. Read more about this interesting modification at http://www.planetquake.com/relay/.
In this document I'll discuss the DM2 format used by Quake II. There are significant differences between the Quake II test release (engine version 3.00) and the CD retail version (3.05). This difference consists especially in the reordered message code bytes, so that the ``normal codes'' have the values 1 to 5 in 3.05. There are some more differences in the spawnbaseline message. Due to the existence of a sole DM2 file in the 3.00 format (packed in the PAK file of the test release) and considering the fact, that the test release can't even record new ones, I will confine myself to the ``new'' format, introduced with the CD retail version 3.05 (protocol version 26).
With 3.15 (protocol version 32) a new kind of DM2 file was introduced: server side recordings. These are for DM2 editors only, since they contain all information from all entities but no player information and Quake II isn't even able to play them back.
In 3.17 (protocol version 33) the server side recordings became easier to parse because serverdata can now be used to distinguish between client side and server side recordings.
Table 1. Covered Quake II versions.
version | platform | note | |
3.05 x86 Nov 30 1997 RELEASE | Win32 | CD retail | |
3.06 x86 Dec 9 1997 RELEASE | Win32 | first patch | |
3.07 x86 Dec 27 1997 RELEASE | Win32 | interim release | |
3.08 x86 Dec 28 1997 RELEASE | Win32 | another interim release | |
3.09 x86 Dec 29 1997 RELEASE | Win32 | with patch program | |
3.10 x86 Jan 4 1998 RELEASE | Win32 | last before point release | |
3.10 NON-WIN32 Jan 5 1998 NON-WIN32 | Linux | first Linux release | |
3.12 x86 Feb 16 1998 RELEASE | Win32 | point release | |
3.13 x86 Feb 23 1998 RELEASE | Win32 | point fix release | |
3.13 i386 Feb 24 1998 RELEASE | Linux | point fix release | |
3.14 i386 Mar 2 1998 RELEASE | Linux | another fix release | |
3.14 x86 Mar 2 1998 RELEASE | Win32 | another fix release | |
3.14 i386 Mar 3 1998 RELEASE | Linux | bug fix release 3.14a | |
3.15 x86 May 27 1998 Win32 RELEASE | Win32 | Mission Pack 1 release | |
3.15 i386 May 29 1998 Linux | Linux | bug fix release 3.15a | |
3.17 i386 Jun 21 1998 Linux | Linux | stable release | |
3.17 x86 Jun 22 1998 Win32 RELEASE | Win32 | stable release | |
3.18 x86 Aug 19 1998 Win32 RELEASE | Win32 | Mission Pack 2 release | |
3.19 x86 Sep 1 1998 Win32 RELEASE | Win32 | bug fix release | |
3.19 x86 Sep 10 1998 Linux | Linux | bug fix release | |
3.20 x86 Oct 9 1998 Linux | Linux | final release(equal to 3.20 beta) | |
3.20 x86 Oct 16 1998 Win32 RELEASE | Win32 | final release(equal to 3.20 beta) |
As the clever reader may know I'm the author of LMPC, the Little Movie Processing Centre. With this tool you may
``decompile'' an existing DM2 file (3.05 ... 3.20) to a simple text file and
``compile'' such a (modified) text file back to a binary DM2 file.
To describe the file structure, which is very complicated, I use C like program fragments and struct definitions. This simplifies my task a lot.
I invented all used names (messages, variables etc.) for myself, took them from the Quake II binary but almost all from the many published source code snippets.
All multi-byte structures in DM2 files are ``little endian'' (VAX or Intel ordered, lowest byte first),
Descriptive sentences in italics are copied from the published game source. There are several different source packages available from id Software:
the oldest from december 1997 with the source of the game library and some tools (unpacked in the directory q2source_12_11):
general version: ftp://ftp.idsoftware.com/pub/quake2/source/old/q2source_12_11.zip
the newer one from march 1998 with the source of the game library only:
The newest release from the end of november 1998 with both Mission Packs:
Plain game (unpacked in the directory q2src320/game/)
Linux version: ftp://ftp.idsoftware.com/pub/quake2/source/q2src320.shar.Z
Windows version: ftp://ftp.idsoftware.com/pub/quake2/source/q2src320.exe
Xatrix Quake II Mission Pack 1: The Reckoning (unpacked in the directory xatrixsrc320/game/)
Linux version: ftp://ftp.idsoftware.com/pub/quake2/source/xatrixsrc320.shar.Z
Windows version: ftp://ftp.idsoftware.com/pub/quake2/source/xatrixsrc320.exe
Rouge Quake II Mission Pack 2: Ground Zero (unpacked in the directory roguesrc320/game)
Linux version: ftp://ftp.idsoftware.com/pub/quake2/source/roguesrc320.shar.Z
Windows version: ftp://ftp.idsoftware.com/pub/quake2/source/roguesrc320.exe
And now some QuakeEd compliant coordinate typedef's:
typedef float vec_t; typedef vec_t vec3_t[3];
A DM2 file is a set of ``blocks'' of ``messages''. A block consists of a 4 byte length entry and the actual messages:
typedef struct { unsigned long size; unsigned char messages[size]; } block_t;
is the maximum message size (real game data in a network packet). The value must be less than or equal to 1472 to avoid fragmentation of the UDP packets on an Ethernet. The MTU in PPP is much smaller and QuakeWorld used up to 2.10 also much bigger network packets but the reduced packet size is for LAN play definitely a great improvement. In server side recordings there is not such a restriction.
if (serverdata.isdemo != RECORD_SERVER && block.size>MAX_MSGLEN) error("Demo message > MAX_MSGLEN");
Beginning with version 3.05 (protocol version 26) Quake II uses an empty block with a block size of -1 (0xffffffff) as the last block. This remindes (at least) me of the DOOM LMP end byte (0x80).
Between 2 levels of a multi-level recoring there is an empty block with the block size 0. Quake II can't replay multi-level recordings.
Here comes the definition of some small auxiliary routines to simplify the main message description. get_next_unsigned_char, get_next_signed_char, get_next_short and get_next_long, get_next_float are basic functions and they do exactly what they are called. Please note: byte, char or short will be converted to long.
In the following I often use a count variable
int i;without declaration. I hope this does not confuses you.
int ReadChar { return (int) get_next_signed_char; }
int ReadByte { return (int) get_next_unsigned_char; }
int ReadShort { return (int) get_next_short; }
int ReadLong { return (int)get_next_long; }
float ReadFloat { return get_next_float; }
The string reading stops at '\0' or after 0x7FF bytes. The internal buffer has only 0x800 bytes available.
char* ReadString { char* string_pointer; char string_buffer[0x800]; string_pointer=string_buffer; for (i=0 ; i<0x7FF ; i++, string_pointer++) { if (! (*string_pointer = ReadChar) ) break; } *string_pointer = '\0'; return strdup(string_buffer); }
vec_t ReadCoord { return (vec_t) ReadShort * 0.125; }
ReadPosition(vec_t* pos) { for (i=0 ; i<3 ; i++) pos[i] = ReadCoord; }
A direction for temporary entities is stored in a single byte. The 162 possible orientations are precalculated and stored in a template list. Each direction is represented by a normalised vector. The template list can be found in the source, q2source_12_11/utils3/qdata/anorms.h
I tried to reproduce (for WriteDir) the template list but I didn't find the algorithm behind it. Take it as it is: even Quake II uses the list and calculates with dot products the best fitting direction.
ReadDir(vec_t* pos) { #define NUMVERTEXNORMALS 162 int code; float avertexnormals[NUMVERTEXNORMALS][3] = { #include "q2source_12_11/utils3/qdata/anorms.h" } code = ReadByte; if (code >= NUMVERTEXNORMALS) error("MSF_ReadDir: out of range"); pos[0] = avertexnormals[code][0]; pos[1] = avertexnormals[code][1]; pos[2] = avertexnormals[code][2]; }
Note: A signed angle in a single byte. There are only 256 possible direction to look into.
vec_t ReadAngle { return (vec_t) ReadChar * 360.0 / 256.0; }
vec_t ReadAngle16 { return (vec_t) ReadShort * 360.0 / 65536.0; }
This is the message structure:
typedef struct { unsigned char ID; unsigned char unicast_client // RLA only char messagecontents[???]; } message_t;The length of a message depends on its type (or ID) but it can't be bigger than MAX_MSGLEN, the size of a full block.
RLA files (DM2 files with Relay extension) can have special uni-cast messages. This is done to distinguish between ordinary messages, which go to every client (multi-cast) and messages, which go only to selected clients. A good example are the playerinfo messages. Every client should get a different one and in RLA files all these messages are marked with the destination client number:
ID = ReadByte; if (ID & 0x80) { // the special sign for uni-cast ID &= ~0x80; // remove it unicast.use = 1; // but memorize it unicast.client = ReadByte; // get the uni-cast destination client } else { unicast.use = 0; // multi-cast message }
The easiest way to explain a message is to give a short C like program fragment to parse such a message. It is not really the same code base as in LMPC but it should be very similar. Future version will be based on the exact same code base to simplify my task.
Each message can be described by its ID or its name.
0x00
Something is bad. This message should never appear.
error("CL_ParseServerMessage: Illegible server message\n");
0x01
muzzle flashes / player effects.
is the player entity with the effect.
is the muzzle flash / player effect. The value should be one of the MZ_ constants (source, q2src320/game/q_shared.h, 602 - 638). It may be ``OR''ed with
#define MZ_SILENCED 128if the player uses the Silencer.
entity = ReadShort; value = ReadByte;
0x02
monster muzzle flashes.
is the monster entity with the effect.
is the monster muzzle flash. The value should be one of the MZ2_ constants (source, q2src320/game/q_shared.h, 640 - 871).
entity = ReadShort; value = ReadByte;
0x03
Spawns a temporary entity. Temp entity events are for things that happen at a location seperate from any existing entity. Temporary entity messages are explicitly constructed and broadcast.
Due to the special parse method I use in my LMPC program, I have to use the variables over all different temporary entities in the same order. This was no real restriction but with game version 3.18 (protocol version 34) came many new types of temporary entities, which reordered their arguments almost random. So I had to rename some variables and to insert some totally senseless new ones (like flash_entity instead of entity or type in addition to style). This simply comes, because these values come now at the other side of origin. The old order was entity, origin, style but now a style comes sometimes before the origin or an entity comes after the origin. After I finish the text parse rewrite of LMPC, I will surely remove the additional senseless new variables.
is the type of the temporary entity. entitytype should be one of the TE_ constants (source, q2src320/game/q_shared.h, 876 - 942). They are defined with a typedef enum and not as #define but who cares?
There are many different kinds of temporary entities in Quake II:
is a point like entity like explosions or teleport fog. It needs a position.
boss, barrels etc. explosion
barrels explosion
rocket explosion
grenade explosion
rocket explosion under water
grenade explosion under water
BFG explosion
big BFG explosion
boss teleports away
(new in 3.15, MP1) plasma explosion on touch
(new in 3.18, MP2) flame burst, turret death
(new in 3.18, MP2) this spits out some smoke from the motor. it's a two-stroke, you know.
(new in 3.18, MP2) tracker explosion
(new in 3.18, MP2) teleport away effect
(new in 3.18, MP2) ball disappears after a goal
(new in 3.18, MP2) nuke explosion
(new in 3.18, MP2) last part of the death of the widow
(new in 3.18, MP2) big explosion
(new in 3.18, MP2) widow explosion
is a small point like entity, spawned on an impact of a bullet etc. It needs a position (often a trace end) and a direction from there.
hit with Machine Gun and Chain Gun
clients and monsters bleed when hit
hit with Blaster
hit with Shotgun
hit someone
hit on an Energy Armor
hit on a different Energy Armor
hit someone with bullets
(new in 3.15, MP1) a ``gekk'' bleeds green. Note: the value 26 stood from game version 3.12 to 3.14 (protocol version 31) for TE_PLASMATRAIL and was a line entity.
(from 3.12 to 3.14) not used. Note: the value 27 stands from game version 3.15 (protocol version >= 32) on for TT_BLUEHYPERBLASTER and is a line entity.
(new in 3.18, MP2) hit by green monster blaster shot
(new in 3.18, MP2) more blood with chainfist
(new in 3.18, MP2) heat beam water impact
(new in 3.18, MP2) heat beam impact
(new in 3.18, MP2) touch a flechette
is a 2 dimensional entity. It needs an origin and an end position.
trail of the Rail Gun (blue)
shot with a bullet through water
(new in 3.12) BFG laser
(from 3.12 to 3.14) not used. Note: the value 26 stands from game version 3.15 (protocol version >= 32) on for TE_GREENBLOOD_new and is an impact entity.
(new in 3.15, MP1) not used. Note: the value 27 stood from game version 3.12 to 3.14 (protocol version 31) for TE_GREENBLOOD_old and was an impact entity.
(new in 3.18, MP2) debug use, trail drawing
(new in 3.18, MP2) bubbles above heat beam
(new in 3.18, MP2) hit on something mechanical
is a entity which doesn't fit into the other categories.
creates a particle splash effect
laser hits a wall
parasite attack
medic cable attac
(new in 3.12) used in CFT
(new in 3.12) ionripper fire, fixbot melder fire
(new in 3.15, MP1) trigger effect
(new in 3.18, MP2) flame thrower, Quake II can't parse this
(new in 3.18, MP2) tesla weapon
(new in 3.18, MP2) player with torch
(new in 3.18, MP2) force wall
(new in 3.18, MP2) heat beam
(new in 3.18, MP2) monster fires a heat beam
(new in 3.18, MP2) Creates a steam effect (particles w/ velocity in a line).
(new in 3.18, MP2) part of the death of the widow
is a entity which is not used anymore.
(new in 13.18, MP2) not used, Quake II can't parse this
is the entity, where an effect ends.
is the entity, which spawned the effect.
is a special value to chain multiple TE_STEAM effects. The last one has here the value -1.
is a number for multi-particle temporary entities.
is the start of the unparsable temporary TE_FLAME entity.
is the origin of an entity, where the temporary entity ends.
is an additional type value for the temporary TE_WIDOWBEAMOUT entity. number for multi-particle temporary. It may be 20001 or 20002 (source, roguesrc320/game/m_widow.c, 831 - 842).
is the origin of the temporary entity.
is the entity, which spawned the temporary TE_FLASHLIGHT entity.
is the end position of the line like temporary entity.
are additional positions for bigger enitities.
is the moving direction for the temporary entity.
is an additional style value. In the special TE_SPLASH temporary entity it should be one of the SPLASH_ constants (source, q2src320/game/q_shared.h, 944 - 950).
are used for the TE_STEAM temporary entity.
is a timeout value (in milliseconds) for the TE_STEAM temporary entity.
entitytype = ReadByte; switch (entitytype) { // version problems // case TE_PLASMATRAIL: case TE_GREENBLOOD_new: if (serverversion >= 32) // game version >= 3.15 goto impact_entity; else goto line_entity; break; // case TE_GREENBLOOD_old: case TE_BLUEHYPERBLASTER: if (serverversion >= 32) // game version >= 3.15 goto line_entity; else goto impact_entity; break; // point entity case TE_EXPLOSION1: case TE_EXPLOSION2: case TE_ROCKET_EXPLOSION: case TE_GRENADE_EXPLOSION: case TE_ROCKET_EXPLOSION_WATER: case TE_GRENADE_EXPLOSION_WATER: case TE_BFG_EXPLOSION: case TE_BFG_BIGEXPLOSION: case TE_BOSSTPORT: case TE_PLASMA_EXPLOSION: case TE_PLAIN_EXPLOSION: case TE_CHAINFIST_SMOKE: case TE_TRACKER_EXPLOSION: case TE_TELEPORT_EFFECT: case TE_DBALL_GOAL: case TE_NUKEBLAST: case TE_WIDOWSPLASH: case TE_EXPLOSION1_BIG: case TE_EXPLOSION1_NP: ReadPosition(origin); break; // impact entity case TE_GUNSHOT: case TE_BLOOD: case TE_BLASTER: case TE_SHOTGUN: case TE_SPARKS: case TE_SCREEN_SPARKS: case TE_SHIELD_SPARKS: case TE_BULLET_SPARKS: // case TE_GREENBLOOD_new: // case TE_GREENBLOOD_old: case TE_BLASTER2: case TE_MOREBLOOD: case TE_HEATBEAM_SPARKS: case TE_HEATBEAM_STEAM: case TE_ELECTRIC_SPARKS: case TE_FLECHETTE: impact_entity: ReadPosition(origin); ReadDir(movedir); break; // line entity case TE_RAILTRAIL: case TE_BUBBLETRAIL: case TE_BFG_LASER: // case TE_PLASMATRAIL: // case TE_BLUEHYPERBLASTER: case TE_DEBUGTRAIL: case TE_BUBBLETRAIL2: line_entity: ReadPosition(origin); ReadPosition(trace_endpos); break; // special entity case TE_SPLASH: case TE_LASER_SPARKS: case TE_WELDING_SPARKS: case TE_TUNNEL_SPARKS: count = ReadByte; ReadPosition(origin); ReadDir(movedir); style = ReadByte; break; case TE_PARASITE_ATTACK: case TE_MEDIC_CABLE_ATTACK: case TE_HEATBEAM: case TE_MONSTER_HEATBEAM: entity = ReadShort; ReadPosition(origin); ReadPosition(trace_endpos); break; case TE_GRAPPLE_CABLE: entity = ReadShort; ReadPosition(origin); ReadPosition(trace_endpos); ReadPosition(pos1); break; case TE_FLAME: // Quake2 can't parse this! entity = ReadShort; count = ReadShort; ReadPosition(start); ReadPosition(origin); ReadPosition(pos1); ReadPosition(pos2); ReadPosition(pos3); ReadPosition(pos4); break; case TE_LIGHTNING: dest_entity = ReadShort; entity = ReadShort; ReadPosition(dest_origin); ReadPosition(origin); break; case TE_FLASHLIGHT: ReadPosition(origin); flash_entity = ReadShort; break; case TE_FORCEWALL: ReadPosition(origin); ReadPosition(trace_endpos); style = ReadShort; break; case TE_STEAM: nextid = ReadShort; count = ReadByte; ReadPosition(origin); ReadDir(movedir); style = ReadByte; plat2flags = ReadShort; if (nextid != -1) wait = ReadLong; break; case TE_WIDOWBEAMOUT: type = ReadShort; ReadPosition(origin); break; case TE_RAILTRAIL2: // senseless, I know default: error("CL_ParseTEnt: bad type"); break; }
0x04
This message displays the Field Computer (``cmd help'', bound to F1). It contains the summary screen (Deathmatch Scoreboard or single player secrets, goals etc.). It stores the message only on the client siede. The actual display will be triggered by the playerinfo message with a special stats command.
is the summary screen text with some kind of control characters. The control language is very simple. Read some examples in the source, q2src320/game/p_hud.c, functions DeathmatchScoreboardMessage and HelpComputer.
text = ReadString;
0x05
Tells the clients its inventory.
is the player's inventory array.
#define MAX_ITEMS 256is the number different items in the inventory.
The inventory concept of Quake II is very open and expandable. In an global array (source, q2src320/game/g_items.c, 1115 - 2097)
gitem_t itemlist[];is the list of all possible things a player can pickup and carry around. This array will be filled at compile time. In the inventory array are stored how many of each thing of the itemlist a players carries. The server tells the meaning of each index with configstring messages. I give the original table for itemlist. Every modification can change it totally and it changed indeed from 3.05 to 3.12.
Table 2. Standard inventory entries.
value | purpose |
0 | not used |
1 | Body Armor |
2 | Combat Armor |
3 | Jacket Armor |
4 | Armor Shard |
5 | Power Screen |
6 | Power Shield |
7 | Blaster |
8 | Shotgun |
9 | Super Shotgun |
10 | Machinegun |
11 | Chaingun |
12 | Grenades |
13 | Grenade Launcher |
14 | Rocket Launcher |
15 | HyperBlaster |
16 | Railgun |
17 | BFG10K |
18 | Shells |
19 | Bullets |
20 | Cells |
21 | Rockets |
22 | Slugs |
23 | Quad Damage |
24 | Invulnerability |
25 | Silencer |
26 | Rebreather |
27 | Environment Suit |
28 | Ancient Head |
29 | Adrenaline |
30 | Bandolier |
31 | Ammo Pack |
32 | Data CD |
33 | Power Cube |
34 | Pyramid Key |
35 | Data Spinner |
36 | Security Pass |
37 | Blue Key |
38 | Red Key |
39 | Commander's Head |
40 | Airstrike Marker |
41 | Health |
for (i=0 ; i<MAX_ITEMS ; i++) inventory[i] = ReadShort;
0x07
Diconnect from the server.
print("Server disconnected\n");
0x08
Reconnect to the server.
print("Server disconnected, reconnecting\n");
0x09
Plays a sound.
is a bit mak to reduce the network traffic.
is the index in the precache sound table.
is the volume of the sound (0.0 off, 1.0 max).
is the attenuation of the sound. attenuation should have one of the ATTN_ constants (source, q2src320/game/q_shared.h, 966 - 970).
full volume the entire level
the normal attenuation
for idle monsters
diminish very rapidly with distance
is the offset in seconds between the frame start and the sound start. It is 0 in the entire source.
is the sound channel. There are 8 possible sound channels for each entity in Quake II (0-7) but it uses 5 only. channel should be one of the CHAN_ constants (source, q2src320/game/q_shared.h, 953 - 960).
selects a channel automatically
weapon use sounds
pain calls
item get sounds
jump and fall sounds
is the entity which caused the sound. The maximum value of entity is
#define MAX_EDICTS 1024.
is the origin of the sound.
long entity_channel; // combined variable mask = ReadByte; soundnum = ReadByte; vol = (mask & 0x01) ? ((float)ReadByte / 255.0) : (1.0); attenuation = (mask & 0x02) ? ((float)ReadByte / 64.0) : (1.0); timeofs = (mask & 0x10) ? ((float)ReadByte * 0.001) : (0.0); if (mask & 0x08) { entity_channel = ReadShort; entity = (entity_channel >> 3); channel = entity_channel & 0x07; if (entity > MAX_EDICTS) { error("CL_ParseStartSoundPacket: ent = %i", entity); } } else { channel = 0; entity = 0; } if (mask & 0x04) { ReadPosition(origin); }
0x0A
Prints a text at the top of the screen.
is the priority level. level should be one of the following:
pickup messages
death messages
critical messages
chat messages
is the the text to be displayed.
level = ReadByte; if (level == PRINT_CHAT) sound("misc/talk.wav"); string = ReadString;
0x0B
The client transfers the text to the console and runs it.
is the command, which the client has to execute.
text = ReadString;
0x0C
Set some global info.
is the protocol version coming from the server.
some kind of key. Will be used again later in a stufftext message for the login hand-shake.
indicates the demo type. Possible values are:
data actually over the wire (proxy)
recorded on the client side. Containes information in the direct neighborhood of the recording player.
recorded on the server side. Containes all information on all entities in the level. These files tend to become very large. They can't be played back by Quake II directly. This value appears in 3.17 but server side recordings work from 3.15 on. This variable is sometimes called attractloop for whatever reason.
recorded with the Relay modificiation at the server side. These files can't be played back only with the Relay modification.
is the game directory (may be empty, which means baseq2).
is the client id.
is the name of the map.
compatibility with the CD retail version 3.05 (protocol 26). How to set this? ??FIXME??
log("Serverdata packet received.\n"); serverversion = ReadLong; if (!compatible) { if (serverversion != PROTOCOL_VERSION) error("Server returned version %i, not %i", serverversion, PROTOCOL_VERSION); } } key = ReadLong; isdemo = ReadByte; game = ReadString; client = ReadShort; mapname = ReadString;
0x0D
config strings are a general means of communication from the server to all connected clients. Each config string can be at most MAX_QPATH characters.
is the number of the config string. The following constants (source, q2src320/game/q_shared.h, 1069 - 1092) determine where to find something in the full array of config strings.
is the name of the level.
is the audio CD track for this level.
is the sky texture.
is the sky axis in the %f %f %f format.
is the rotation speed in the format %f.
is the start of the list of display program strings for the statusbar.
is the current maximum number of clients on a server.
is the map checksum for catching cheater maps.
is the start of the precache model table.
is the maximum number of the precache model table.
(288) is the start of the precache sound table.
is the maximum number of the precache sound table.
(544) is the start of the image list.
is the maximum numer of the images.
(800) is the start of the light styles list.
is the maximum number of the light styles.
(1056) is the start of the items list.
is the maximum number of items in the inventory list.
(1312) is the start of the player skin list.
is the maximum number of players.
(1568) is the start of the general config strings list.
(512) is the maximum number of general config strings.
(2080) is the maximum number of config strings.
is the corresponding config string.
#define MAX_QPATH 64is the maximum length of a config string.
index = ReadShort; if (index > MAX_CONFIGSTRINGS) error("configstring > MAX_CONFIGSTRINGS"); string = ReadString;
0x0E
Spawns a new entity.
is a bit-mask to reduce the network traffic.
is the number of the entity.
is the origin.
is the orientation.
is the old origin for lerping. It is used for client-side prediction and interpolation calculations. To compress a DM2 file, just leave it out, if the last frame had a origin entry for the entity in question. It is used with cl_nodelta 1 only. From 3.17 on old_origin is used for player entities only.
is the model index.
is weapons, CTF, flags, etc.
is weapons, CTF, flags, etc.
is weapons, CTF, flags, etc.
is the frame of the model.
is the number of the skin for the model.
is the number of visual weapon skin for the model.
Effects are things handled on the client side (lights, particles, frame animations) that happen constantly on the given entity. An entity that has effects will be sent to the client even if it has a zero index model. The bit-mask effects holds the EF_ constants (source, q2src320/game/q_shared.h, 530 - 569).)
are some special render flags. The bit-mask renderfx holds the RF_ constants (source, q2src320/game/q_shared.h, 571 - 591).
for client side prediction, 8*(bits 0-4) is x/y radius 8*(bits 5-9) is z down distance, 8(bits10-15) is z up
for looping sounds, to guarantee shutoff
impulse events -- muzzle flashes, footsteps, etc events only go out for a single frame, they are automatically cleared each frame event should have one of the following values (source, q2src320/game/q_shared.h, 1098 - 1112):
typedef enum { EV_NONE, // 0 EV_ITEM_RESPAWN, // 1 EV_FOOTSTEP, // 2 EV_FALLSHORT, // 3 EV_FALL, // 4 EV_FALLFAR, // 5 EV_PLAYER_TELEPORT // 6 } entity_event_t;
mask = ReadByte; if (mask & 0x00000080) mask |= (ReadByte << 8); if (mask & 0x00008000) mask |= (ReadByte << 16); if (mask & 0x00800000) mask |= (ReadByte << 24); entity = (mask & 0x00000100) ? ReadShort : ReadByte; if (mask & 0x00000800) modelindex = ReadByte; if (mask & 0x00100000) modelindex2 = ReadByte; if (mask & 0x00200000) modelindex3 = ReadByte; if (mask & 0x00400000) modelindex4 = ReadByte; if (mask & 0x00000010) frame = ReadByte; if (mask & 0x00020000) frame = ReadShort; if (mask & 0x00010000) { if (mask & 0x02000000) skin = ReadLong; else skin = ReadByte; } else { if (mask & 0x02000000) skin = ReadShort; } vwep = skin >> 8; skin &= 0xFF; if (mask & 0x00004000) { if (mask & 0x00080000) effects = ReadLong; else effects = ReadByte; } else { if (mask & 0x00080000) effects = ReadShort; } if (mask & 0x00001000) { if (mask & 0x00040000) renderfx = ReadLong; else renderfx = ReadByte; } else { if (mask & 0x00040000) renderfx = ReadShort; } if (mask & 0x00000001) origin[0] = ReadCoord; if (mask & 0x00000002) origin[1] = ReadCoord; if (mask & 0x00000200) origin[2] = ReadCoord; if (mask & 0x00000400) angles[0] = ReadAngle; if (mask & 0x00000004) angles[1] = ReadAngle; if (mask & 0x00000008) angles[2] = ReadAngle; if (mask & 0x01000000) ReadPosition(old_origin); if (mask & 0x04000000) sound = ReadByte; event = (mask & 0x00000020) ? ReadByte : 0; if (mask & 0x08000000) solid = ReadShort;
0x0F
Prints the specified text at the centre of the screen. There is only one text line with a maximum of 40 characters. To print more than this one line, use `\n' in a single centerprint message for a new line. Every text line (the first 40 characters) will be centred horizontally.
is the text to be displayed.
text = ReadString;
0x10
Download a file (sound, model etc.) from the server. It needs at least version 3.15 to transfer any data. There is no position information in the packet, so the client can't rearrange packets, which arrive in the wrong order. Therefore all download network packets are reliable packets and they need the usual acknowledgement. The client itself starts the whole thing, if something is missing and asks the server for it. I'm not sure, if such blocks may actually do something useful in a DM2 file.
is the number of bytes transferred in the current packet.
is the total amount sended in percent.
is a buffer for downloaded files.
is the name for the downloaded file.
is the file pointer for the downloaded file.
size = ReadShort; percent=ReadByte; if (size == -1) { error("File not found.\n"); } if (serverdata.serverversion >= 32) { // game version >= 3.15 if (percent == 0) { fp = fopen(filename, "wb"); } for ( i=0 ; i<size ; i++ ) { downloadbuffer[i] = ReadByte; } fwrite(fp, size, 1, downloadbuffer); if (percent != 100) { servercommand("nextdl"); // ask for the next part } else { fclose(fp); } }
0x11
Player info. Have to come directly after a frame message in client recording. There is no such message in server side recordings since there is no special player who does the recording.
is a bit mask to reduce the network traffic.
is a bit mask to reduce the network traffic.
is important for client side prediction and should be one of the PM_ constants (source, q2src320/game/q_shared.h, 440 - 451).
is the origin.
is the velocity.
ducked, jump_held, etc and should be one of the PMF_ constants (source, q2src320/game/q_shared.h, 453 - 460).
is unknown (each unit = 8 ms).
is the gravity (800, cvar sv_gravity).
add to command angles to get view direction, changed by spawns, rotating objects, and teleporters
for fixed views
add to pmovestate->origin
add to view direction to get render angles set by weapon kicks, pain effects, etc
direction of weapon
offset of weapon
model index of weapon
frame of weapon
rgba full screen effect
horizontal field of view. Since the field of view is no client side variable (as it is in Quake), it is much easier to create zoom effects in movies.
refdef flags, should be one of the RDF_ constants (source, q2src320/game/q_shared.h, 593 - 600).
fast status bar updates Each entry in this array stays for something on the statusbar. The value of an icon entry is the image index defined in a configstring message. A value of 0 on an icon entry switches the icon off. (source, q2src320/game/q_shared.h, 973 - 993)
maximum number of things in the status bar
health icon image
health value
ammo icon image
ammo value
armour icon
armour value
icon image of the selected weapon
icon image of a recently gotten thing
configstring index to describe the recently gotten thing, 0 = string off
icon image of timed object (Quad Damage, Invulnerability etc.)
timer value for a timed object (in seconds)
help icon: 0 off, 1 on
item number of the selected weapon
layout activator: 0 off, 1 display help/summary screen, 2 display inventory
client score value
flash the backgrounds behind the status numbers, cleared each frame, 1 = health, 2 = armor, 0 = off
chase target skin number (number in configstring list)
0 = normal play, 1 = spectator
mask = ReadShort; if (mask & 0x0001) pm_type = ReadByte; if (mask & 0x0002) ReadPostion(origin); if (mask & 0x0004) ReadPosition(velocity); if (mask & 0x0008) teleport_time = ReadByte; if (mask & 0x0010) pm_flags = ReadByte; if (mask & 0x0020) gravity = ReadShort; if (mask & 0x0040) { delta_angles[0] = ReadAngle16; delta_angles[1] = ReadAngle16; delta_angles[2] = ReadAngle16; } if (mask & 0x0080) { viewoffset[0] = ReadChar / 4.0; viewoffset[1] = ReadChar / 4.0; viewoffset[2] = ReadChar / 4.0; } if (mask & 0x0100) { viewangles[0] = ReadAngle16; viewangles[1] = ReadAngle16; viewangles[2] = ReadAngle16; } if (mask & 0x0200) { kick_angles[0] = ReadChar / 4.0; kick_angles[1] = ReadChar / 4.0; kick_angles[2] = ReadChar / 4.0; } if (mask & 0x1000) gunindex = ReadByte; if (mask & 0x2000) { gunframe = ReadByte; gunoffset[0] = ReadChar / 4.0; gunoffset[1] = ReadChar / 4.0; gunoffset[2] = ReadChar / 4.0; gunangles[0] = ReadChar / 4.0; gunangles[1] = ReadChar / 4.0; gunangles[2] = ReadChar / 4.0; } if (mask & 0x0400) { blend[0] = ReadByte / 255.0; blend[1] = ReadByte / 255.0; blend[2] = ReadByte / 255.0; blend[3] = ReadByte / 255.0; } if (mask & 0x0800) fov = ReadByte; if (mask & 0x4000) rdflags = ReadByte; mask2 = ReadLong; for (i=0;i<32;i++) if (mask2 & (0x00000001 << i)) stats[i] = ReadShort;
0x12
Entity updates. Have to come in a client side recording directly after a playerinfo message and in a server side recording directly after a frame message. This message is in fact a list of spawnbaseline messages. Look there for a longer variable description. The list ends with entity==0.
is used to reduce the network traffic.
is the number of the entity.
indicates a disappearing entity.
is the origin.
is the orientation.
old origin (for client side prediction).
is the model index.
is weapons, CTF, flags, etc.
is weapons, CTF, flags, etc.
is weapons, CTF, flags, etc.
is the frame of the model.
is the number of the skin for the model.
is the number of visual weapon skin for the model.
is the entity effect.
are some special render flags.
for client side prediction, 8*(bits 0-4) is x/y radius 8*(bits 5-9) is z down distance, 8(bits10-15) is z up
for looping sounds, to guarantee shutoff
impulse events -- muzzle flashes, footsteps, etc events only go out for a single frame, they are automatically cleared each frame
for (;;) { mask = ReadByte; if (mask & 0x00000080) mask |= (ReadByte << 8); if (mask & 0x00008000) mask |= (ReadByte << 16); if (mask & 0x00800000) mask |= (ReadByte << 24); entity = (mask & 0x00000100) ? ReadShort : ReadByte; if (entity >= MAX_EDICTS) error("CL_ParsePacketEntities: bad number:%i",entity); if (entity == 0) break; remove = (mask & 0x00000040) ? 1 : 0; if (mask & 0x00000800) modelindex = ReadByte; if (mask & 0x00100000) modelindex2 = ReadByte; if (mask & 0x00200000) modelindex3 = ReadByte; if (mask & 0x00400000) modelindex4 = ReadByte; if (mask & 0x00000010) frame = ReadByte; if (mask & 0x00020000) frame = ReadShort; if (mask & 0x00010000) { if (mask & 0x02000000) skin = ReadLong; else skin = ReadByte; } else { if (mask & 0x02000000) skin = ReadShort; } vwep = skin >> 8; skin &= 0xFF; if (mask & 0x00004000) { if (mask & 0x00080000) effects = ReadLong; else effects = ReadByte; } else { if (mask & 0x00080000) effects = ReadShort; } if (mask & 0x00001000) { if (mask & 0x00040000) renderfx = ReadLong; else renderfx = ReadByte; } else { if (mask & 0x00040000) renderfx = ReadShort; } if (mask & 0x00000001) origin[0] = ReadCoord; if (mask & 0x00000002) origin[1] = ReadCoord; if (mask & 0x00000200) origin[2] = ReadCoord; if (mask & 0x00000400) angles[0] = ReadAngle; if (mask & 0x00000004) angles[1] = ReadAngle; if (mask & 0x00000008) angles[2] = ReadAngle; if (mask & 0x01000000) ReadPosition(old_origin); if (mask & 0x04000000) sound = ReadByte; event = (mask & 0x00000020) ? ReadByte : 0; if (mask & 0x08000000) solid = ReadShort; }
0x13
Entity updates. May come after a packetentities block. It isn't really necessary since packetentities handles itself all the delta encoding.
unknown ??FIXME??
0x14
Sequence numbers to cope with UDP packet loss, delta encoding and portal border crossings. Start of the playerinfo and packetentities messages.
In server side recordings this message contains only the current frame number.
In Relay recordings, this message looks like the client side frame message but it contains also the list of connected clients.
is the sequence number of the current packet or frame. Since the Quake II server uses a fixed time gap of 100ms (10Hz) between game state changes seq1 / 10 is the time in seconds since the server started.
is the sequence number of the delta reference frame. The reference holds for both following playerinfo and packetentities messages. The Quake II server has a table with some old entity states and get from each client the sequence number of frames, which arrived correctly at the client side. So the server can decide which most currently sended old frame should be used now as the delta encoding reference frame. seq2 is -1 to reference to the spawnbaseline values.
is the number of bytes in the areas array.
is the maximum number of areas in a map.
is the array which defines the areas to be rendered. Each bit in the areas array stays for one area in the map.
is the frame number in server side recordings and similar to seq1. I may rename it even to seq1 in a future revision.
is the number of connected clients in the connected array. It is used in Relay recordings only.
is the array, which contains the numbers of the connected clients. It is used in Relay recordings only.
long uk_b1; if (serverdata.isdemo == RECORD_CLIENT || serverdata.isdemo == RECORD_NETWORK || serverdata.isdemo == RECORD_RELAY) { seq1 = ReadLong; seq2 = ReadLong; if (serverdata.serverversion != 26) uk_b1 = ReadByte; count = ReadByte; for (i=0;i<count;i++) areas[i] = ReadByte; if (serverdata.isdemo == RECORD_RELAY) { connected_count = ReadByte; for (i=0;i<connected_count;i++) connected[i] = ReadByte; } } if (serverdata.isdemo == RECORD_SERVER) { frame = ReadLong; }
First version (working paper) completed.
Only based on the published game source.
Never ever published.
Old 3.00 ID codes included.
Version info included.
All simple ID codes ok.
packetentities, deltapacketentities, playerinfo missing.
spawnbaseline is very difficult.
Never published.
Old 3.00 ID codes removed. Nobody wants to know them.
spawnbaseline is ok now.
playerinfo ok.
packetentities should be ok.
ReadDir for temp_entity ok.
deltapacketentities missing.
Never published.
SGML-Tools 1.0.2 used for formatting.
Some minor tweaks.
SGML-Tools 1.0.2 formatting problems solved.
Long #define tables removed.
More references to the source.
Some details better. Many thanks to Kekoa Proundfoot (kekoa@graphics.stanford.edu).
PlanetQuake is the new home.
Compatible with Quake II up to version 3.14.
SGML-Tools 1.0.5 used.
Thanks to Ben Swartzlander (swartz@rice.edu) for the config string index 30.
Changed some command names (sound volume, server protocol version, config string, print text).
Changed the coordinates in playerinfo to standard coordinates.
Most changes in variable types and names were necessary to reduce the total number of tokens in the Quake and Quake II text parser of LMPC.
Compatible with Quake II up to 3.15a.
server side recording information included.
Compatible with Quake II up to 3.17.
Some hints on old_origin updates and sequence numbering.
SGML-Tools 1.0.7 used.
More block size hints.
Visual Weapon (VWep) support.
Small cosmetic corrections.
Some hints to multi-level recordings.
Missing auxiliary function ReadCoord included.
More versions analysed.
Compatible with Quake II up to 3.20 with Mission Packs 1 and 2.
New published source code incorporated.
SGML-Tools 1.0.9 used.
General clean-up.
New DM2 format variant for Quake II Relay modification included.
Thanks to Conor Davis (cedavis@planetquake.com) for the information about the Quake II Relay project.