To create a recording of your network play use up to QuakeWorld 2.10 the console command record name server. This connects you to the server server and records the game play from your point of view into the file name.qwd.
From version 2.20 on you have first to connect to the server and start the recording with the console command record name later.
The recording stops when you disconnect from the server or you use the console command stop. To play it back, use the commands playdemo name or timedemo name.
Table 1. Covered QuakeWorld versions
Exe: | version | platform |
12:43:52 Jun 13 1997 | Linux QuakeWorld (0.94) 1.64 | Linux, SVGA |
19:51:52 Aug 7 1997 | Linux QuakeWorld 2.00 | Linux, SVGA |
23:46:07 Oct 13 1997 | Linux QuakeWorld (0.94) 2.10 | Linux, SVGA |
00:03:05 Oct 14 1997 | Linux QuakeWorld (0.94) 2.10 | Linux, X11, 8bpp |
23:46:22 Oct 23 1997 | QuakeWorld Server 2.10 | Linux |
18:32:34 Nov 2 1997 | Linux QuakeWorld (0.94) 2.10 | Linux, OpenGL |
14:47:43 May 16 1998 | QuakeWorld Server 2.20 | Linux |
14:48:28 May 16 1998 | Linux (0.96) QuakeWorld 2.20 | Linux, SVGA |
14:48:28 May 16 1998 | Linux (0.96) QuakeWorld 2.20 | Linux, X11 |
14:50:20 May 16 1998 | Linux (0.96) QuakeWorld 2.20 | Linux, OpenGL |
14:37:00 May 20 1998 | QuakeWorld Server 2.21 | Linux |
14:37:46 May 20 1998 | Linux (0.97) QuakeWorld 2.21 | Linux, SVGA, 8bpp |
14:39:38 May 20 1998 | Linux (0.97) QuakeWorld 2.21 | Linux, OpenGL |
17:27:37 Jul 13 1998 | QuakeWorld Server 2.29BETA | Linux |
17:27:56 Jul 13 1998 | Linux (0.98) QuakeWorld 2.29 | Linux, SVGA |
17:27:56 Jul 13 1998 | Linux (0.98) QuakeWorld 2.29 | Linux, X11 |
17:28:42 Jul 13 1998 | Linux (0.98) QuakeWorld 2.29 | Linux, OpenGL |
17:05:37 Aug 26 1998 | QuakeWorld Server 2.30 | Linux |
17:06:04 Aug 26 1998 | Linux (0.98) QuakeWorld 2.30 | Linux, SVGA |
17:06:04 Aug 26 1998 | Linux (0.98) QuakeWorld 2.30 | Linux, X11 |
17:07:08 Aug 26 1998 | Linux (0.98) QuakeWorld 2.30 | Linux, OpenGL (Mesa) |
17:07:08 Aug 26 1998 | Linux (0.98) QuakeWorld 2.30 | Linux, OpenGL (any) |
I actually write and check my documentation with Linux qwcl and qwsv 2.30 on the same machine.
The older versions of QuakeWorld couldn't record a QWD file but playback seemed to work. I checked QuakeWorld 1.54c and 1.55 with a QWD file recorded with 1.64 but both old versions crashed on playback.
If you find that this documentation covers even more versions (other operating systems) please drop me a note.
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 QWD file to a simple text file and
``compile'' such a (modified) text file back to a binary QWD file.
The QWD format is very different from the original Quake DEM format. That applies as well to the internal message format as to the general game scene representation. Many things are now described totally different, which makes an accurate converter (particles, nails etc.) very difficult.
I know for sure that I'll never write a DEM <-> QWD converter.
A string may contain any 8 bit characters except `\377' and it ends with `\000'. The special characters `\n' and `\r' have their normal meaning.
The QuakeWorld font is an extended ASCII font (7 bit) which contains in the upper half a similar font but with a different colour.
I used a simple Quake DEM file to print all 252 ASCII characters.
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 QuakeWorld binary, QuakeEd but almost all from the QuakeC source.
All multi-byte structures in QWD files are ``little endian'' (VAX or Intel, lowest byte first).
At first some QuakeEd compliant coordinate typedef's:
typedef float vec_t; typedef vec_t vec3_t[3];
A QWD file is the recording of the network traffic between the client and the server in both directions. Each network packet and its time stamp will be stored in a `block' of the QWD file.
Every block has the structure
typedef struct { float time; char code; char data[???]; } block_t;
is the time stamp of the block.
is the sign to indicate the block type. Each block type will be parsed totally different.
is actual data of the block.
A client block is 41 bytes long and contains an expanded version of a network packet from the client to the server. The actual network packet is packed.
The client block has the following structure:
typedef struct { float time; char code; // == 0 long load; vec3_t angles; short speed[3]; unsigned char flag; unsigned char impulse; vec3_t uk_angles; } client_block_t;
is connected somehow with the workload on the client. ?FIXME?
point in the viewing direction (in degree) of the client.
is the intended translation of the client (forward, right, up).
is a collection of flags and must be splitted:
fire = (flag & 0x01) ? 1 : 0; jump = (flag & 0x02) ? 1 : 0;Other bits may contain additional information.
indicates an attack console command.
indicates a jump console command.
is the value of a currently activated impulse console command.
A server block has a variable length and contains a copy of a network packet from the server to the client. It has the following structure:
typedef struct { float time; char code; // == 1 long blocksize; unsigned long seq_rel_1; char messages[blocksize-4]; } server_block_t;
is the number of bytes in the block following the blocksize variable itself (but including seq_rel_1). The full server block has blocksize+9 bytes. The maximim value for blocksize (MAX_MSGLEN) used to be 7500 but changed from protocol 25 to 26 (game version 2.10 to 2.20) to 1450. The new value avoids fragmentation of the UDP packets on an Ethernet. The MTU in PPP is much smaller but the reduced packet size is for LAN play definitely a great improvement.
is a sign to distinguish between a connectionless block (==0xFFFFFFFF) or a game block (!=0xFFFFFFFF).
Each connectionless block or packet contains one server command to control the server-client network connection and has the structure
typedef struct { float time; char code; // == 1 long blocksize; unsigned long seq_rel_1; // == 0xFFFFFFFF char connless_id; char connless_data[blocksize-5]; } connless_block_t;
is a code to explain the following connless_data.
is the rest of the block and depends on the value of connless_id.
A game block has the structure
typedef struct { float time; char code; // == 1 long blocksize; unsigned long seq_rel_1; // != 0xFFFFFFFF unsigned long seq_rel_2; char messages[blocksize-8]; } game_blocks_t;
is a compound variable and must be splitted:
seq1 = seq_rel_1 & 0x7FFFFFFF; reliable1 = ( seq_rel_1 >> 31 ) & 0x01;
is the sequence code of the sent network packet (from the server to the client).
Indicates, that this packet is a reliable one. In the actual network protocol, the server retransmit it until the client send the acknowledge.
is a compound variable and must be splitted:
seq2 = seq_rel_2 & 0x7FFFFFFF; reliable2 = ( seq_rel_2 >> 31 ) & 0x01;
is the sequence code of the last received network packet (from the client to the server).
Indicates, that this packet was a reliable one. In the actual network protocol, the server acknowledge so a reliable packet from the client.
contain several game messages with the main game data. The structure is similar to the messages in Quake DEM files.
A frame block is 13 bytes long and contains 2 sequence numbers. It is a server to client block and the first sequence number goes up in the same sequence as in all the surrounding game blocks. The exact meaning of these sequence numbers is unknown to me. ?FIXME? A frame block appears first in protocol version 26 (game version 2.20).
The frame block has the following structure:
typedef struct { float time; char code; // == 2 unsigned long seq1; unsigned long seq2; } frame_block_t;
is the current sequence number (from the server to the client).
is certainly some kind of an already received sequence number.
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, get_next_long and 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. Second note: Don't look at the variable types for size calculations. Look at the routine names.
In the following I often use a count variable
int i;without declaration. I hope this does not confuses you.
long ReadByte { return (long) get_next_unsigned_char; }
long ReadChar { return (long) get_next_signed_char; }
long ReadShort { return (long) get_next_short; }
long ReadLong { return get_next_long; }
Note: A signed angle in a single byte. There are only 256 possible direction to look into.
vec_t ReadAngle { return (vec_t) ReadChar / 256.0 * 360.0; }
This angle can point in 65536 directions.
vec_t ReadAngle16 { return (vec_t) ReadShort / 65536.0 * 360.0; }
A coordinate is stored in 16 bits: 1 sign bit, 12 integer bits and 3 fraction bits.
vec_t ReadCoord { return (vec_t) ReadShort * 0.125; }
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); }
long ReadFloat { return get_next_float; }
This is the general message structure:
typedef struct { char connless_id; char connless_data[???]; } connless_message_t;The length of a message depends on its type.
The easiest way to explain a message in a connectionless block 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. Each message can be described by its connless_id or its name.
0x02
Stop the playback. It is usually the last block of a QWD file.
is an unused and (by QuakeWorld) unparsed text. Its value is "EndOfDemo".
text=ReadString;
0x42 = 'B'
The client transfers the text to the console and runs it. The command can only come from a local server.
is the console command, to be executed.
text=ReadString;
0x63 = 'c'
This special message appears first with protocol version 26 (game version 2.20).
challenge = ReadString;
0x6A = 'j'
The server tells the client to start the game initialisation procedure.
none
0x6B = 'k'
The server tells the client that it is still alive.
none
0x6E = 'n'
The client transfers the text to the console and prints it.
is the text to be printed.
text=ReadString;
This is the general game message structure:
typedef struct { unsigned char ID; char messagecontent[????]; } game_message_t;The length of a message depends on its type (or ID).
Each message can be described by its ID or its name.
0x00
Something is bad. This message should never appear.
error("CL_ParseServerMessage: Bad server message");
0x03
Updates directly a byte value in the player state array of long numbers. To update a long value, look in section Section 5.39.
is the index in the playerstate array. Table `updatestat indices' lists all possible indices and their real name. The value of an items entry (index=15) is a bit difficult and will be explained in the table `items bits'.
Table 3. updatestat indices
index | variable |
0 | health |
1 | ??? (not used) |
2 | weaponmodel |
3 | currentammo |
4 | armorvalue |
5 | weaponframe |
6 | ammo_shells |
7 | ammo_nails |
8 | ammo_rockets |
9 | ammo_cells |
10 | weapon |
11 | total_secrets |
12 | total_monsters |
13 | found_secrets |
14 | killed_monsters |
15 | items |
. | |
. | |
. | |
23 | ??? |
. | |
. | |
. | |
31 | ??? |
Table 4. items bits
bit | value | QuakeC | purpose |
0 | 0x00000001 | IT_SHOTGUN | Shotgun (should be always 1) |
1 | 0x00000002 | IT_SUPER_SHOTGUN | Double-barrelled Shotgun |
2 | 0x00000004 | IT_NAILGUN | Nailgun |
3 | 0x00000008 | IT_SUPER_NAILGUN | Perforator |
4 | 0x00000010 | IT_GRENADE_LAUNCHER | Grenade Launcher |
5 | 0x00000020 | IT_ROCKET_LAUNCHER | Rocket Launcher |
6 | 0x00000040 | IT_LIGHTNING | Thunderbolt |
7 | 0x00000080 | IT_EXTRA_WEAPON | extra weapon (there is no extra weapon) |
8 | 0x00000100 | IT_SHELLS | Shells are active |
9 | 0x00000200 | IT_NAILS | Nails are active |
10 | 0x00000400 | IT_ROCKETS | Grenades are active |
11 | 0x00000800 | IT_CELLS | Cells are active |
12 | 0x00001000 | IT_AXE | Axe (should be always 1) |
13 | 0x00002000 | IT_ARMOR1 | green Armor |
14 | 0x00004000 | IT_ARMOR2 | yellow Armor |
15 | 0x00008000 | IT_ARMOR3 | red Armor |
16 | 0x00010000 | IT_SUPERHEALTH | Megahealth |
17 | 0x00020000 | IT_KEY1 | silver keycard (or runekey or key) |
18 | 0x00040000 | IT_KEY2 | gold keycard (or runekey or key) |
19 | 0x00080000 | IT_INVISIBILITY | Ring of Shadows |
20 | 0x00100000 | IT_INVULNERABILITY | Pentagram of Protection |
21 | 0x00200000 | IT_SUIT | Biosuit |
22 | 0x00400000 | IT_QUAD | Quad Damage |
23 | 0x00800000 | unknown | unknown (is 0) |
24 | 0x01000000 | unknown | unknown (is 0) |
25 | 0x02000000 | unknown | unknown (is 0) |
26 | 0x04000000 | unknown | unknown (is 0) |
27 | 0x08000000 | unknown | unknown (is 0) |
28 | 0x10000000 | unknown | Rune 1 |
29 | 0x20000000 | unknown | Rune 2 |
30 | 0x40000000 | unknown | Rune 3 |
31 | 0x80000000 | unknown | Rune 4 |
is the new (byte) value.
is the array to describe the player state.
index = ReadByte; if (index > 31) error("CL_SetStat: %i is invalid", index); value = ReadByte; playerstate[index] = value;
0x06
This message starts the play of a sound at a specific point.
is the volume of the sound (0.0 off, 1.0 max).
is the attenuation of the sound.
is the sound channel. There are 8 possible sound channels for each entity in QuakeWorld but it uses 5 only.
Table 6. Sound channels
value | QuakeC | purpose |
0 | CHAN_AUTO | selects a channel automatically |
1 | CHAN_WEAPON | weapon use sounds |
2 | CHAN_VOICE | pain calls |
3 | CHAN_ITEM | item get sounds |
4 | CHAN_BODY | jump and fall sounds |
Channel 0 never willingly overrides. Other channels (1-4) always override a playing sound on that channel.
is the entity which caused the sound.
is the index in the precache sound table.
is the origin of the sound.
long entity_channel; // combined variable entity_channel = ReadShort; vol = entity_channel & 0x8000 ? (float) ReadByte / 255.0 : 1.0; attenuation = entity_channel & 0x4000 ? (float) ReadByte / 64.0 : 1.0; channel = entity_channel & 0x07; entity = (entity_channel >> 3) & 0x03FF; if (entity >= 0x0300) error("CL_ParseStartSoundPacket: ent = %i", entity); soundnum = ReadByte; for (i=0 ; i<3 ; i++) origin[i] = ReadCoord;
0x08
The client prints the text in the top left corner of the screen. There is space for 4 lines. They scroll up and the text disappears. The text will be printed on the console as well.
is the priority level of the text.
is the text to be displayed. All font specials are explained in section Section 2.
level = ReadByte; text = ReadString;
0x09
The client transfers the text to the console and runs it.
is the command, which the client has to execute.
text = ReadString;
0x0A
This message set the camera orientation.
is the new camera orientation.
for (i=0 ; i<3 ; i++) angles[i] = ReadAngle;
0x0B
This message initialises a new level.
is the protocol version coming from the server.
is the number of levels analysed since the existence of the server process. Starts with 1.
is the QuakeWorld game directory. It has usually the value "qw";
is the client id.
is the name of the level.
is the maximum running speed. It may be changed during the game with the maxspeed message.
is the gravity in the level. It may be changed during the game with the entgravity message.
are other global definition variables. ?FIXME?
serverversion = ReadLong; if (serverversion != PROTOCOL_VERSION) error("Server returned version %i, not %i", version, PROTOCOL_VERSION); age = ReadLong; game = ReadString; client = ReadByte; mapname = ReadString; if (serverversion >= 25) { // from 2.00 on var0 = ReadFloat; var1 = ReadFloat; maxspeed = ReadFloat; var3 = ReadFloat; var4 = ReadFloat; var5 = ReadFloat; var6 = ReadFloat; var7 = ReadFloat; var8 = ReadFloat; entgravity = ReadFloat; }
0x0C
This message defines a light animation style.
is the light style number.
is a string of letters `a' .. `z', where `a' means black and `z' white. All effects from nervous flashing (``az'') to slow dimming (``zyxwvu ... edcba'') can so be described.
is the last number number of a light style.
style = ReadByte; if (style>MAX_LIGHTSTYLES) error("svc_lightstyle > MAX_LIGHTSTYLES"); string = ReadString;
0x0E
This message updates the frag count of a specific player.
is the player number (0 .. MAX_SCOREBOARD).
is the new frag count for this player.
is the last possible number of a player.
player = ReadByte; if (player>MAX_SCOREBOARD) error("CL_ParseServerMessage: svc_updatefrags > MAX_SCOREBOARD"); frags = ReadShort;
0x10
Stops a sound. It looks for a sound started with a sound message with the same channel and entity.
is the sound channel.
is the entity which caused the sound.
long channel_entity; // combined variable channel_entity = ReadShort; channel = channel_entity & 0x07; entity = (channel_entity >> 3) & 0x03FF;
0x13
Tells how severe was a hit and from which point it came.
will be subtracted from the current armor.
will be subtracted from the current health.
is the origin of the hit. It points to the weapon (not the origin) of the attacking entity or it is (0,0,0) if the damage was caused by drowning or burning.
save = ReadByte; take = ReadByte; for (i=0 ; i<3 ; i++) origin[i] = ReadCoord;
0x14
This message creates a static entity and sets the internal values.
is the number of already started static entities. The maximum number is 127.
is the array filled up with the data of the static entities.
is the model index in the precache model table for the entity.
is the frame number of the model.
is the colormap number to display the model.
is the skin number of the model.
is the origin of the entity.
is the orientation of the entity.
if (StaticEntityCount > 127) error("Too many static entities"); staticentities[StaticEntityCount].default_modelindex = ReadByte; staticentities[StaticEntityCount].default_frame = ReadByte; staticentities[StaticEntityCount].default_colormap = ReadByte; staticentities[StaticEntityCount].default_skin = ReadByte; for (i=0 ; i<3 ; i++) { staticentities[StaticEntityCount].default_origin[i] = ReadCoord; staticentities[StaticEntityCount].default_angles[i] = ReadAngle; } StaticEntityCount++;
0x16
Creates a dynamic entity and sets the internal default values.
is the number of the entity. In Quake there was a test if this number is too big. There is no such test in QuakeWorld.
is the array filled up with the data of the dynamic entities.
is the model index in the precache model table for the entity.
is the frame number of the model.
is the colormap number to display the model.
is the skin number of the model.
is the origin of the entity.
is the orientation of the entity.
entity = ReadShort; entities[entity].default_modelindex = ReadByte; entities[entity].default_frame = ReadByte; entities[entity].default_colormap = ReadByte; entities[entity].default_skin = ReadByte; for (i=0 ; i<3 ; i++) { entities[entity].default_origin[i] = ReadCoord; entities[entity].default_angles[i] = ReadAngle; }
0x17
Creates a temporary entity.
is the type of the temporary entity. There are three kinds of temporary entities:
is a small point like entity.
Table 9. point entities
value | QuakeC | purpose |
0 | TE_SPIKE | unknown |
1 | TE_SUPERSPIKE | superspike hits (spike traps) |
3 | TE_EXPLOSION | grenade/missile explosion |
4 | TE_TAREXPLOSION | explosion of a tarbaby |
7 | TE_WIZSPIKE | wizard's hit |
8 | TE_KNIGHTSPIKE | hell knight's shot hit |
10 | TE_LAVASPLASH | Chthon awakes and falls dead |
11 | TE_TELEPORT | teleport end |
13 | TE_LIGHTNINGBLOOD | hit by Thunderbolt |
is a two-dimensional entity.
is a cluster of point entities.
is the entity which created the temporary entity.
is the origin of the entity.
is the destination of the line entity.
is the number of particles in a multi entity.
entitytype = ReadByte; switch (entitytype) { case 0,1,3,4,7,8,10,11,13: for (i=0 ; i<3 ; i++) origin[i] = ReadCoord; break; case 5,6,9: entity = ReadShort; for (i=0 ; i<3 ; i++) origin[i] = ReadCoord; for (i=0 ; i<3 ; i++) trace_endpos[i] = ReadCoord; break; case 2,12: count = ReadByte; for (i=0 ; i<3 ; i++) origin[i] = ReadCoord; break; default: error("CL_ParseTEnt: bad type"); break; }
0x18
Set the pause state. This message was not implemented up to game version 2.10 (protocol version 25) but appears from game version 2.20 (protocol version 26) on.
is non-zero to start the pause and zero to stop it.
pausestate = ReadByte; if (pausestate) { // pause is on } else { // pause is off }
0x1A
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.
All font specials are explained in section Section 2.
is the text to be displayed.
text = ReadString;
0x1B
Indicates the death of a monster.
is the number of killed monsters. It may be displayed with the console command showscores.
killed_monsters++;
0x1C
Indicates, that the player just entered a secret area. It comes usually with a centerprint message.
is the number of found secrets. It may be displayed with the console command showscores.
found_secrets++;
0x1D
This message starts a static (ambient) sound not connected to an entity but to a position.
is the origin of the sound.
is the sound index in the precache sound table.
is the volume (0.0 off, 1.0 max)
is the attenuation of the sound. Possible attenuations can be found in the table `Sound attenuations' of section Section 5.7.
for (i=0 ; i<3 ; i++) origin[i] = ReadCoord; soundnum = ReadByte; vol = (float) ReadByte / 255.0; attenuation = (float) ReadByte / 64.0;
0x1E
Displays the level end screen.
is the origin of the intermission waiting place.
is the viewing direction from the intermission waiting place.
for ( i=0 ; i<3 ; i++) origin[i] = ReadCoord; for ( i=0 ; i<3 ; i++) angles[i] = ReadAngle;
0x1F
Displays the episode end screen and some text. The text will be printed like centerprint but slower.
is the episode end text.
text = ReadString;
0x20
Selects the audio CD track number.
is the audio CD track to play.
track = ReadByte;
0x22
The recoil of the Shotgun, Nailgun, Perforator, Grenade Launcher, Rocket Launcher and Thunderbolt kicks the shooter soft.
none
0x23
The recoil of the Double-barrelled Shotgun kicks the shooter hard.
none
0x24
Updates the ping time. ?FIXME?
is the number of the player (0 .. MAX_SCOREBOARD).
is the ping time in milliseconds.
player = ReadByte; if (player>MAX_SCOREBOARD) error("CL_ParseServerMessage: svc_updateping > MAX_SCOREBOARD"); ping = ReadShort;
0x25
Defines the start time, when the client enters the server. ?FIXME?
is the number of the player (0 .. MAX_SCOREBOARD).
is the time stamp, as the client enters a server.
player = ReadByte; if (player>MAX_SCOREBOARD) error("CL_ParseServerMessage: svc_updateentertime > MAX_SCOREBOARD"); entertime = ReadFloat;
0x26
Updates directly a long value in the player state array of long numbers. To update a byte value, look in section Section 5.4.
is the index in the playerstate array. Look in table `updatestat indices' in section Section 5.4 for a list of possible indices.
is the new (long) value.
is the array to describe the player state.
index = ReadByte; if (index > 31) error("CL_SetStat: %i is invalid", index); value = ReadLong; playerstate[index] = value;
0x27
The entity lights up a bit when it shoots.
is the entity number.
entity = ReadShort;
0x28
Updates some user information from the master server.
is the player number (0 .. MAX_SCOREBOARD).
is 0, if there is no such player connected. Some kind of user identification. ?FIXME?
is a string with variable definitions, separated by `\'. These variables define the color, the name etc.
player = ReadByte; user = ReadLong; if (player>MAX_SCOREBOARD) error("CL_ParseServerMessage: svc_updateuserinfo > MAX_SCOREBOARD"); text = ReadString;
0x29
Download a file from the server.
is the length of the data block. A typical value is 1024.
is the transferred amount of data in percent.
is the buffer for downloaded files.
size = ReadShort; percent = ReadByte; if (size == -1) error("File not found.\n"); } 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); }
0x2A
Updates player information.
is the number of the player (0 .. MAX_SCOREBOARD).
is a bit mask to reduce the network traffic.
is the origin of the player.
is the frame number of the player model.
has something to do with the ping time. ?FIXME?
is another bit mask to reduce the network traffic.
is connected somehow to the workload of the client. ?FIXME?
point in the viewing direction (in degree) of the client.
is the translation of the client (forward, right, up).
is a collection of flags and must be splitted.
indicates an attack console command.
indicates a jump console command.
is the value of a currently activated impulse console command.
is the current speed in the x,y, and z direction. Used to predict the future.
is the model index in the precache model table of the player.
is the model index in the precache model table for the standard value "progs/player.mdl".
is an unknown byte. ?FIXME?
contains a bit mask for the current weapon.
Table 12. weapon bits
bit | value | QuakeC | weapon |
? | 0x00 | not available | Axe |
0 | 0x01 | IT_SHOTGUN | Shotgun |
1 | 0x02 | IT_SUPER_SHOTGUN | Double-barrelled Shotgun |
2 | 0x04 | IT_NAILGUN | Nailgun |
3 | 0x08 | IT_SUPER_NAILGUN | Perforator |
4 | 0x10 | IT_GRENADE_LAUNCHER | Grenade Launcher |
5 | 0x20 | IT_ROCKET_LAUNCHER | Rocket Launcher |
6 | 0x40 | IT_LIGHTNING | Thunderbolt |
7 | 0x80 | IT_EXTRA_WEAPON | extra weapon (there is no extra weapon) |
is the frame of the current weapon model.
player = ReadByte; mask = ReadShort; for (i=0;i<3;i++) origin[i] = ReadCoord; frame = ReadByte; if (mask & 0x0001) ping = ReadByte * 0.001; // bit 0 if (mask & 0x0002) { // bit 1 mask2 = ReadByte; if (serverdata.serverversion >= 27) { // from game version 2.29BETA on if (mask2 & 0x01) angles[0] = ReadAngle16; // bit 0 if (mask2 & 0x80) angles[1] = ReadAngle16; // bit 7 if (mask2 & 0x02) angles[2] = ReadAngle16; // bit 1 if (mask2 & 0x04) speed[0] = ReadShort; // bit 2 if (mask2 & 0x08) speed[1] = ReadShort; // bit 3 if (mask2 & 0x10) speed[2] = ReadShort; // bit 4 if (mask2 & 0x20) flag = ReadByte; // bit 5 fire = (flag & 0x01) ? 1 : 0; jump = (flag & 0x02) ? 1 : 0; if (mask2 & 0x40) impulse = ReadByte; // bit 6 load = ReadByte); } else { // serverdata.serverversion <= 26, game version up to 2.21 if (mask2 & 0x01) angles[0] = ReadAngle16; // bit 0 angles[1] = ReadAngle16; if (mask2 & 0x02) angles[2] = ReadAngle16; // bit 1 if (mask2 & 0x04) speed[0] = ReadByte; // bit 2 if (mask2 & 0x08) speed[1] = ReadByte; // bit 3 if (mask2 & 0x10) speed[2] = ReadByte; // bit 4 if (mask2 & 0x20) flag = ReadByte; // bit 5 fire = (flag & 0x01) ? 1 : 0; jump = (flag & 0x02) ? 1 : 0; if (mask2 & 0x40) impulse = ReadByte; // bit 6 if (mask2 & 0x80) load = ReadByte; // bit 7 } } if (mask & 0x0004) cspeed[0] = ReadCoord; // bit 2 if (mask & 0x0008) cspeed[1] = ReadCoord; // bit 3 if (mask & 0x0010) cspeed[2] = ReadCoord; // bit 4 model = (mask & 0x0020) ? ReadByte : playermodel; // bit 5 uk_byte6 = (mask & 0x0040) ? ReadByte : 0; // bit 6 if (mask & 0x0080) weapon = ReadByte; // bit 7 if (mask & 0x0100) weaponframe = ReadByte; // bit 8
0x2B
Describes the position and orientation of all currently flying nails.
struct { vec3_t origin; float angle_1; float angle_2; } nail_t;
is the number of nails.
is the internal array with all nail coordinates.
is the origin of the nail.
is the tilt angle of the nail.
is the yaw angle of the nail.
unsigned char b[5]; int j; nail_t* n; nailcount = ReadByte; for (j=0,n=nails;j<nailcount;j++,n++) { for (i=0;i<5;i++) b[i] = ReadByte; // 3 12 bit values n->origin[0] = (b[0] & 0xFF) | ((b[1] & 0x0F) << 8); n->origin[1] = ((b[1] & 0xF0) >> 4) | (b[2] << 4); n->origin[2] = (b[3] & 0xFF) | ((b[4] & 0x0F) << 8); // shift and scale to standard (even) coordinates for (i=0;i<3;i++) n->origin[i] = (n->origin[i] - 2048) * 2; // signed value in 4 bits n->angle_1 = (b[4] & 0xF0) >> 4; // respect the sign if (n->angle_1>=8) n->angle_1 = n->angle_1 - 16; // scale it n->angle_1 *= 360.0 / 16.0; n->angle_2 = ReadAngle; }
The structure of the nail message is so strange, that I can't suppress some general remarks.
Table 13. the 6 nails bytes
byte | bit 7 ... 4 | bit 3 ... 0 |
0 | origin[0] b7 ... b4 | origin[0] b3 ... b0 |
1 | origin[1] b3 ... b0 | origin[0] b11 ... b8 |
2 | origin[1] b11 ... b8 | origin[1] b7 ... b4 |
3 | origin[2] b7 ... b4 | origin[2] b3 ... b0 |
4 | angle_1 b3 ... b0 | origin[2] b11 ... b8 |
5 | angle_2 b7 ... b4 | angle_2 b3 ... b0 |
A standard coordinate is a signed 16 bit number with 1 sign bit, 12 integer bits and 3 fraction bits. Nail coordinates have only 12 bits in total. The lowest 4 bits of the 16 bit coordinate (the 3 fraction bits and the odd/even bit) are set to zero. Therefore a nail can only exist on an even integer coordinate. The sign transformation is not the usual one. The half nail coordinate is shifted in the positive range by 2048 before the network transmission.
The tilt angle of a fast flying nail is not very important. There are only 4 bits to describe the tilt angle and only 3 of them are used at all. The 4 bits form a normal (but very short) signed integer. To compute an angle in degree back from this short value, it must be multiplied by 360/16=22.5. The range of values of a tilt angle of a nail is [-90,90] and not [-180,180]. Therefore bit 2 of angle_1 is always equal to bit 3 (the sign bit). So the eight possible values for the tilt angle are -90.0, -67.5, -45.0, -22.5, 0.0, 22.5, 45.0, 67.5.
The yaw angle is a standard 1 byte signed integer angle. It must be multiplied by the usual 360/256.
0x2C
There is an internal list with 64 entries. Each entry describes a full state (with all 32 players and such). This message defines how many entries in this list can be canceled, because they now contain irrelevant information.
is the number of entries to cancel.
choke = ReadByte;
0x2D
Reads (a part of) the precache model table.
is the precache model table. It will be filled up with model file names. Many other messages contain an index in this array. The first used index is 1. Beginning with protocol version 26 (game version 2.20) QuakeWorld uses a new parse method and sends an unused index first.
is the number of models in the precache model table.
is the index of the first model.
is the index for the next group of models. It is 0 to end the precache model table.
if (serverdata.serverversion) >= 26) { // 2.20 and higher char *text; first = ReadByte(m); for ( i=first ; i<256 ; i++ ) { text = ReadString; if (strlen(text) == 0) break; precache_models[i+1] = strdup(text); // store model one position later } next = ReadByte(m); } else { // up to 2.10 nummodels = 0; do { if (++nummodels > 255) error("Server sent too many model_precache"); precache_models[nummodels] = ReadString; } while (*precache_models[nummodels]); }
0x2E
Reads (a part of) the precache sound table.
is the precache sound table. It will be filled up with sound file names. Many other messages contain an index in this array. The first used index is 1. Beginning with protocol version 26 (game version 2.20) QuakeWorld uses a new parse method and sends an unused index first.
is the number of sounds in the precache sound table.
is the index of the first sound.
is the index of the next group of sounds. It is 0 to end the precache sound table.
if (serverdata.serverversion) >= 26) { // 2.20 and higher char *text; first = ReadByte(m); for ( i=index ; i<256 ; i++ ) { text = ReadString; if (strlen(text) == 0) break; precache_sounds[i+1] = strdup(text); // store sound one position later } next = ReadByte(m); } else { // up to 2.10 numsounds = 0; do { if (++numsounds > 255) error("Server sent too many sound_precache"); precache_sounds[numsounds] = ReadString; } while (*precache_sounds[numsounds]); }
0x2F
This message contains the entity numbers in sight and all the changed properties of these entities. The list ends with a 0x0000 for mask.
is a bit mask to reduce the network traffic.
is the entity number.
indicates a disappearing entity.
is the array filled up with all dynamic entities.
is the model index in the precache model table.
is the frame number of the model.
is the colormap number to display the model.
is the skin number of the model.
contains a bit mask for special entity effects.
is the origin of the entity.
is the orientation of the entity.
while (mask = ReadShort) { entity = mask & 0x01FF; mask &= 0xFE00; entities[entity].remove = (mask & 0x4000) ? 1 : 0; if (mask & 0x8000) mask |= ReadByte; if (mask & 0x0004) entities[entity].modelindex = ReadByte; if (mask & 0x2000) entities[entity].frame = ReadByte; if (mask & 0x0008) entities[entity].colormap = ReadByte; if (mask & 0x0010) entities[entity].skin = ReadByte; if (mask & 0x0020) entities[entity].effects = ReadByte; if (mask & 0x0200) entities[entity].origin[0] = ReadCoord; if (mask & 0x0001) entities[entity].angles[0] = ReadAngle; if (mask & 0x0400) entities[entity].origin[1] = ReadCoord; if (mask & 0x1000) entities[entity].angles[1] = ReadAngle; if (mask & 0x0800) entities[entity].origin[2] = ReadCoord; if (mask & 0x0002) entities[entity].angles[2] = ReadAngle; }
0x30
This message contains the changed properties of some entities in sight relative to a specified former frame number. The list ends with a 0x0000 mask.
is the lowest byte of the frame number with the full state, to which this message is the delta.
is a bit mask to reduce the network traffic.
is the array filled up with all dynamic entities.
is the entity number.
indicates a disappearing entity.
is the model index in the precache model table.
is the frame number of the model.
is the colormap number to display the model.
is the skin number of the model.
contains a bit mask for special entity effects.
is the origin of the entity.
is the orientation of the entity.
frame = ReadByte; while (mask = ReadShort) { entity = mask & 0x01FF; mask &= 0xFE00; entities[entity].remove = (mask & 0x4000) ? 1 : 0; if (mask & 0x8000) mask |= ReadByte; if (mask & 0x0004) entities[entity].modelindex = ReadByte; if (mask & 0x2000) entities[entity].frame = ReadByte; if (mask & 0x0008) entities[entity].colormap = ReadByte; if (mask & 0x0010) entities[entity].skin = ReadByte; if (mask & 0x0020) entities[entity].effects = ReadByte; if (mask & 0x0200) entities[entity].origin[0] = ReadCoord; if (mask & 0x0001) entities[entity].angles[0] = ReadAngle; if (mask & 0x0400) entities[entity].origin[1] = ReadCoord; if (mask & 0x1000) entities[entity].angles[1] = ReadAngle; if (mask & 0x0800) entities[entity].origin[2] = ReadCoord; if (mask & 0x0002) entities[entity].angles[2] = ReadAngle; }
0x31
This message contains the maximum players speed.
is the maximum speed. It appears first in protocol version 25 (game version 2.00).
maxspeed = ReadFloat;
0x32
This message defines the current gravity. It appears first in protocol version 25 (game version 2.00).
is the gravity.
gravity = ReadFloat;
0x33
This message set a variable for a specific player. It appears first in protocol version 26 (game version 2.20).
is the player number.
is the name of the variable.
is the value of the variable.
player = ReadByte; name = ReadString; string = ReadString;
0x34
This message set a global variable. It appears first in protocol version 26 (game version 2.20).
is the name of the variable.
is the value of the variable.
name = ReadString; string = ReadString;
0x35
This message updates the player specific packet loss information. It will be displayed in the score-board. It appears first in protocol version 28 (game version 2.30).
is the player number (0 .. MAX_SCOREBOARD).
is the packet loss in percent.
player = ReadByte; if (player>MAX_SCOREBOARD) error("CL_ParseServerMessage: svc_updatepl > MAX_SCOREBOARD"); loss = ReadByte;
First version (working paper) completed.
Many thanks to Olivier Montanuy (Olivier.Montanuy@wanadoo.fr) for his QuakeWorld Network Protocol Specs.
Many thanks to Steffen Winterfeldt (Steffen.Winterfeldt@itp.uni-leipzig.de) for his reverse engineering work. He worked out many of the general structure information.
Many new structure information. sound and nails messages are now correct.
packetentities and deltapacketentities are better now.
Table references.
General clean-up: some variables renamed.
Table references don't work: removed.
QuakeWorld 2.00 info included.
Back again to SGML-Tools 0.99.0.
General clean-up.
PlanetQuake is the new home.
QuakeWorld version table restructured.
SGML-Tools 1.0.5 used.
QuakeWorld version 2.20, 2.21 and 2.29BETA included.
stopsound is OK now.
SGML-Tools 1.0.7 used.
Corrected info on blocksize.
Some small correction with respect to the new LMPC, which can compile QWD files beginning with version 3.1.9.
Removed the senseless ReadEntity function.
QuakeWorld version 2.30 included.
QuakeWorld version 2.30 corrected.
updatepl better.
Many thanks to Tim Holliefield (holliefiel@bad-durkheim.netsurf.de) for some hints on the frame block.
SGML-Tools 1.0.9 used.
setpause corrected. Many thanks to Christer Sandin (czsuch@ocag.ch) for his bug report.
modellist and soundlist corrected. Many thanks to Hoffy (ripple@powerup.com.au) for his bug report. The old token last is called next now, so better recreate all your QWD text files. I know, it is bad to change the text format but last is simply totally wrong.