Attempts to explain the new netcode of the game, new packets and how mods can use them.
Introduction
This guide is an attempt at document the netcode of Quake Enhanced for the purpose of modding and source ports adding compatibility.
There’s no guarantee about the information in this guide being 100% correct or complete.
The guide will be updated as new information comes in.
Overview
The 2021 Re-Release netcode is based off the existing Netquake netcode. It extends it further with more packets and functionality and adds a layer of encryption.
Quake netcode uses UDP for communication. The packets are wrapped around DTLS 1.2 for encryption and security. A PSK (Pre-Shared Key) is used for authentication between server and client. The game uses OpenSSL implementation of DTLS.
Data Types
Quake netcode uses a bunch of datatypes.
Function call | Type | Size in bytes | Callable from QC | Comments |
---|---|---|---|---|
WriteChar | char | 1 | Yes | |
WriteByte | byte | 1 | Yes | |
WriteShort | short | 2 | Yes | |
WriteLong | int | 4 | Yes | |
WriteFloat | float | 4 | No | |
WriteDouble | double | 8 | No | |
WriteString | C-style string | 1+ | Yes | Repeats chars until the byte 0 is found |
WriteCoord | float | 4 | Yes | |
WriteAngle | byte | 1 | Yes | Converts a float using the following formula: ((int)f*256/360) & 255 |
WriteEntity | short | 2 | Yes | Writes out the entity index |
Packets – Client to Server
Packet | Byte value | Description |
---|---|---|
CLC_BAD | 0 | |
CLC_NOP | 1 | |
CLC_DISCONNECT | 2 | |
CLC_MOVE | 3 | |
CLC_STRINGCMD | 4 | |
CLC_UNKNOWN1 | 5 | Appears to be sent every frame |
CLC_UNKNOWN2 | 6 |
Packets – Server to Client
Packet | Byte value | Description |
---|---|---|
SVC_BAD | 0 | |
SVC_NOP | 1 | Used as a heartbeat |
SVC_DISCONNECT | 2 | Sent when the server is closing the connection |
SVC_UPDATESTAT | 3 | |
SVC_VERSION | 4 | |
SVC_SETVIEW | 5 | Tell the client to attach the camera to a specific entity |
SVC_SOUND | 6 | |
SVC_TIME | 7 | |
SVC_PRINT | 8 | Print something |
SVC_STUFFTEXT | 9 | Print on the top-left of the screen |
SVC_SETANGLE | 10 | |
SVC_SERVERINFO | 11 | Contains information about map, maxplayers, which files to precache, and so on |
SVC_LIGHTSTYLE | 12 | |
SVC_UPDATENAME | 13 | Sets a player name on the scoreboard |
SVC_UPDATEFRAGS | 14 | Sets a player frags on the scoreboard |
SVC_CLIENTDATA | 15 | |
SVC_STOPSOUND | 16 | |
SVC_UPDATECOLORS | 17 | |
SVC_PARTICLE | 18 | Creates a particle emission effect |
SVC_DAMAGE | 19 | |
SVC_SPAWNSTATIC | 20 | |
SVC_SPAWNBASELINE | 22 | |
SVC_TEMPENTITY | 23 | |
SVC_SETPAUSE | 24 | |
SVC_SIGNONNUM | 25 | |
SVC_CENTERPRINT | 26 | Prints something in the middle of the screen |
SVC_KILLEDMONSTER | 27 | |
SVC_FOUNDSECRET | 28 | |
SVC_SPAWNSTATICSOUND | 29 | |
SVC_INTERMISSION | 30 | |
SVC_FINALE | 31 | |
SVC_CDTRACK | 32 | |
SVC_SELLSCREEN | 33 | |
SVC_CUTSCENE | 34 | |
SVC_SKYBOX | 37 | |
SVC_BOTCHAT | 38 | Displays a text message in chat as coming from a player. |
SVC_SPAWNEDMONSTER | 39 | |
SVC_BONUSFLASH | 40 | |
SVC_FOG | 41 | |
SVC_SPAWNBASELINE2 | 42 | |
SVC_SPAWNSTATIC2 | 43 | |
SVC_SPAWNSTATICSOUND2 | 44 | |
SVC_SETVIEWS | 45 | |
SVC_UPDATEPING | 46 | |
SVC_UPDATESOCIAL | 47 | |
SVC_UPDATEPLINFO | 48 | |
SVC_RAWPRINT | 49 | |
SVC_SERVERVARS | 50 | |
SVC_SEQ | 51 | |
SVC_ACHIEVEMENT | 52 | |
SVC_CHAT | 53 | Prints a message in the chat |
SVC_LEVELCOMPLETED | 54 | Probably used for achievements |
SVC_BACKTOLOBBY | 55 | Tells the client that they should go back to the lobby screen |
SVC_LOCALSOUND | 56 | Plays a sound that is only heard by the client |
SVC_*: A-Z
SVC_BOTCHAT
This packet tells the client to display a chat message as coming from a specific player id.
Offset | Call | Name |
---|---|---|
0 | WriteByte | Packet Id |
1 | WriteByte | Player Id |
2 | WriteShort | String count |
4 | WriteString | String 1 |
… | WriteString | String … |
Example
WriteByte(MSG_ALL,SVC_BOTCHAT); WriteByte(MSG_ALL,0); // Player 0, the host. WriteShort(MSG_ALL,1); // How many strings are present WriteString(MSG_ALL,"Someone is talking for me!");
With string replacement
WriteByte(MSG_ALL,SVC_BOTCHAT); WriteByte(MSG_ALL,0); // Player 0, the host. WriteShort(MSG_ALL,2); // How many strings are present WriteString(MSG_ALL,"My name is {}"); WriteString(MSG_ALL,"JPiolho");
SVC_CENTERPRINT
This packet tells the client to print text in the middle of the screen. It supports string replacements.
Offset | Call | Name |
---|---|---|
0 | WriteByte | Packet ID |
1 | WriteShort | String count |
3 | WriteString | String 1 |
… | WriteString | String … |
Default center print
WriteByte(MSG_ALL,SVC_CENTERPRINT); WriteShort(MSG_ALL,1); // Number of strings that will follow WriteString(MSG_ALL,"Hello world");
With string replacement
WriteByte(MSG_ALL,SVC_CENTERPRINT); WriteShort(MSG_ALL,2); // Number of strings that will follow WriteString(MSG_ALL,"Welcome to the game, {}"); WriteString(MSG_ALL,"JPiolho");
SVC_CHAT
This packet tells the client to print text in chat. It appears to only work in single player games.
Unlike SVC_BOTCHAT, you have full control over the name of the person plus you can specify some colors.
Available name colors: White (0), Red (1), Light Blue (2), Yellow (3)
Available message colors: White (0), Light blue (1)
Offset | Call | Name |
---|---|---|
0 | WriteByte | Packet ID |
1 | WriteByte | Name color (0-3) |
2 | WriteByte | Message color (0-1) |
3 | WriteString | Name |
… | WriteString | Message |
Default center print
WriteByte(MSG_ALL,53); // SVC_CHAT WriteByte(MSG_ALL,0); // Name color (0-3) WriteByte(MSG_ALL,0); // Message Color (0-1) WriteString(MSG_ALL,"Name"); // Name WriteString(MSG_ALL,"Color 0"); // Message
SVC_UPDATENAME
This packet tells the client to update a player’s name in the scoreboard
Offset | Call | Name |
---|---|---|
0 | WriteByte | Packet Id |
1 | WriteByte | Player Id |
2 | WriteString | Player name |
Example
WriteByte(MSG_ALL,SVC_UPDATENAME); WriteByte(MSG_ALL,0); // Player 0, the host. WriteString(MSG_ALL,"Nobody"); // Set player name to 'Nobody'
Entity packet
This documentation is very incomplete
The entity packet is a bit special. It needs to be sent every frame to the players so that they know it exists. It also specifies all the attributes the client needs in order to display it on the client-side. Things like position, angles, model, solidness, etc…
Instead of having a regular numeric packet id, it relies on the bit 7 of the packet id being set. All the other bits in the byte are used as flags. The size of this packet is highly variable as it depends on the flags that are set.
Start by having a packet id of 128 and then adding different flags that you want.
The packet starts with a flag bitmap of 1 or more bytes. These indicate which entity properties are being sent. After the flag bitmap, the entity id is sent. After this, all the data that was specified in the flag bitmap is sent (in a specific order).
Byte Flagmap 1
Bit | Name |
---|---|
0 | More flag bytes |
1 | Origin X |
2 | Origin Y |
3 | Origin Z |
4 | Angle Y |
5 | No lerp |
6 | Frame |
7 | Entity packet signal (always set to 1) |
If “More flag bytes” bit was set, then the following packet follows:
Byte flagmap 2
Bit | Name |
---|---|
0 | Angle X |
1 | Angle Z |
2 | Model Index |
3 | Color map |
4 | Skin |
5 | Effects |
6 | Is it a long entity? |
8 | More flag bytes |
If “More flag bytes” bit was set, then the following packet follows:
Byte flagmap 3
Bit | Name |
---|---|
0 | Alpha |
1 | Unsure – Frame |
2 | Unsure – Model Index |
3 | Unsure – When the next think will happen |
4 | |
5 | Unsure – Origin Offset |
6 | Solid |
7 | More flag bytes |
If “More flag bytes” bit was set, then the following packet follows:
Byte flagmap 4
Bit | Name |
---|---|
0 | Unsure – Flags |
1 | Unsure – Health |
2 | Unsure – Model Index |
3 | |
4 | |
5 | |
6 | |
7 |
The Entity Id follows. If “Long Entity” bit was set, the entity Id is sent as a Short (2 bytes). If not set then it’s sent as a byte (1 byte).
Now all the information that was specified in the flagmap is sent, in the following order:
Call | If bit is set | Name | Comment |
---|---|---|---|
WriteByte | Byte 2, bit 2 | Model Index | |
WriteByte | Byte 1, bit 6 | Frame | |
WriteByte | Byte 2, bit 3 | Colormap | |
WriteByte | Byte 2, bit 4 | Skin | |
WriteByte | Byte 2, bit 5 | Effects | |
WriteFloat | Byte 1, bit 1 | Origin X | If byte 3 bit 5 is set, then the call is WriteShort |
WriteByte | Byte 2, bit 0 | Angle X | |
WriteFloat | Byte 1, bit 2 | Origin Y | If byte 3 bit 5 is set, then the call is WriteShort |
WriteByte | Byte 1, bit 4 | Angle Y | |
WriteFloat | Byte 1, bit 3 | Origin Z | If byte 3 bit 5 is set, then the call is WriteShort |
WriteByte | Byte 2, bit 1 | Angle Z | |
WriteByte | Byte 3, bit 0 | Alpha | |
WriteByte | Byte 3, bit 1 | Frame | |
WriteByte | Byte 3, bit 2 | Model Index | |
WriteByte | Byte 3, bit 3 | Next Think | |
WriteByte | Byte 3, bit 6 | Solid | |
Unknown | Byte 4, bit 0 | Flags | Appears to send multiple values |
Unknown | Byte 4, bit 1 | Healths | Appears to send multiple values |
Unknown | Byte 4, bit 2 | Model Indexes | Appears to send multiple values |
Example in QuakeC
local float firstBits; local float secondBits; local float thirdBits; firstBits |= 1; // More bytes firstBits |= 1 << 1; // Origin 1 firstBits |= 1 << 2; // Origin 2 firstBits |= 1 << 3; // Origin 3 firstBits |= 1 << 4; // Angle 2 firstBits |= 1 << 6; // Frame secondBits |= (1 << 8) >> 8; // Angle 1 secondBits |= (1 << 9) >> 8; // Angle 3 secondBits |= (1 << 10) >> 8; // Model Index secondBits |= (1 << 11) >> 8; // Colormap secondBits |= (1 << 12) >> 8; // Skin secondBits |= (1 << 13) >> 8; // Effects secondBits |= (1 << 14) >> 8; // Long Entity secondBits |= (1 << 15) >> 8; // More bytes thirdBits |= (1 << 16) >> 16; // Alpha thirdBits |= (1 << 22) >> 16; // Solid msg_entity = self; WriteByte(MSG_ONE,firstBits | 128); // Packet Type // Send all the values if(firstBits & 1) WriteByte(MSG_ONE,secondBits); if(secondBits & 128) WriteByte(MSG_ONE,thirdBits); WriteShort(MSG_ONE,1500 + pNumber); // Entity ID WriteByte(MSG_ONE,modelindex_player); WriteByte(MSG_ONE,p.frame); WriteByte(MSG_ONE,p.colormap); WriteByte(MSG_ONE,p.skin); WriteByte(MSG_ONE,p.effects); WriteCoord(MSG_ONE,p.origin_x); WriteAngle(MSG_ONE,p.angles_x); WriteCoord(MSG_ONE,p.origin_y); WriteAngle(MSG_ONE,p.angles_y); WriteCoord(MSG_ONE,p.origin_z); WriteAngle(MSG_ONE,p.angles_z); WriteByte(MSG_ONE,120); WriteByte(MSG_ONE,SOLID_NOT);
Hope you enjoy the post for Quake New Netcode of The Game + Data Types – Overview, If you think we should update the post or something is wrong please let us know via comment and we will fix it how fast as possible! Thank you and have a great day!
- Check All Quake Posts List
Leave a Reply