Teeworlds 0.7

List of network game messages

There are three types of messages with overlapping message ids:
system messages, game messages and control messages
This list is only covering game messages.

When the client and server exchange packets. An integer field which usually does not exceed one byte. Is indicating which type of message was sent. This field is called the message id. These message ids are defined in generated/protocol.h which is generated by datasrc/network.py which also includes the message payload. There can be multiple messages in so called chunks in one teeworlds packet.
The message names give a hint about who is sending and receiving them. The format is always

                
    NETMSGTYPE_<sender>_<name>
                
            
Where sender can be one of those: In the list below you will find those message. With their name, id and payload.


NETMSG_INVALID

Message ID:0


NETMSGTYPE_SV_MOTD

Sender:Server
Recipient:Client
Message ID:1
Response to: NETMSG_READY |
NETMSG_RCON_CMD sv_motd
Expected response:None
Flags:MSGFLAG_VITAL
Argument name Type Note
Message String The message of the day (motd) that will be displayed to clients that connect. (The one with the dark background)


NETMSGTYPE_SV_BROADCAST

Sender:Server
Recipient:Client
Message ID:2
Response to: NETMSG_RCON_CMD broadcast
Expected response:None
Flags:MSGFLAG_VITAL
Argument name Type Note
Message String The broadcast message.


NETMSGTYPE_SV_CHAT

Sender:Server
Recipient:Client
Message ID:3
Response to: NETMSGTYPE_CL_SAY
Expected response:None
Flags:MSGFLAG_VITAL
Argument name Type Note
Mode Int Chat mode can be one of those
  • 1 - CHAT_ALL
  • 2 - CHAT_TEAM
  • 3 - CHAT_WHISPER
Client ID Int The id of the message author.
Target ID Int Recipient client id of the chat message. If there is no specific target (no whisper message) it is set to -1 which stands for ALL. And set to the target if it is a whipser message.
Message String The text of the chat message.


NETMSGTYPE_SV_TEAM

Sender:Server
Recipient:Client
Message ID:4
Response to: NETMSG_RCON_CMD (swap_teams, set_team, set_team_all, shuffle_teams, force_teambalance) |
NETMSGTYPE_CL_SETTEAM |
Afk moved to spectators
Expected response:None
Flags:MSGFLAG_VITAL
Argument name Type Note
Client ID Int ID of the client that switched team
Team Int Target team the client switched to. Can be one of those:
  • -1 - TEAM_SPECTATORS
  • 0 - TEAM_RED
  • 1 - TEAM_BLUE
If the game mode has no teams and a player joins the game it uses 0 which is technically TEAM_RED.
Silent Int Boolean flag that decides if the message is visible in chat. Can be one of those values:
  • 0 - Message shown (not silent)
  • 1 - Message hidden (silent)
For example if the value 0 is sent it is not silent. And a message like this is shown in the clients chat:

*** 'nameless tee' joined the spectators
Cooldown Tick Int The server disallows changing teams too fast. This field informs the client who changed team when he can change the team again. This is used to show a cooldown in the client menu. The message in the ui then looks like this:

Teams are locked. Time to wait before changing team: 00:01
A sample message sent over the network could look like this:
                
    40 06 0c 08 00 40 00 91 3b   @....@..;
    \______/ ^  ^  ^  ^  \___/
       |     |  |  |  |    |
    chunk    |  |  |  |   Cooldown Tick (3793)
    header   |  |  |  |
flags: vital |  |  |  Silent (0/false)
size: 6      |  |  |
seq: 12      |  | TEAM_SPECTATORS (-1)
             |  |
             | ClientID (0)
             |
        NETMSGTYPE_SV_TEAM (msg=4 system=false)
                
            


NETMSGTYPE_SV_KILLMSG

Sender:Server
Recipient:Client
Message ID:5
Response to: Tee death
Expected response:None
Flags:MSGFLAG_VITAL
Argument name Type Note
Killer Int Client ID of the killer. Can be the same as the Victim. For example on a selfkill with grenade but also when a tee dies in a spike (death tile) or falls out of the world.
Victim Int Client ID of the killed.
Weapon Int Weapon the tee was killed with. Can be one of those:
  • -3WEAPON_GAME (team switching etc)
  • -2WEAPON_SELF (console kill command)
  • -1WEAPON_WORLD (death tiles etc)
  • 0WEAPON_HAMMER
  • 1WEAPON_GUN
  • 2WEAPON_SHOTGUN
  • 3WEAPON_GRENADE
  • 4WEAPON_LASER
  • 5WEAPON_NINJA
Mode special Int For CTF, if the guy is carrying a flag for example. Only when the sv_gametype is ctf this mode is non zero. It is set in ctf.cpp when a flag is involved on death.
This message is used by the server to inform the client about kills. The client will then render them in the kill feed in the top right corner.

The server sends this message to all clients when a tee dies.
                
    void CCharacter::Die(int Killer, int Weapon)
    {
        // we got to wait 0.5 secs before respawning
        m_Alive = false;
        m_pPlayer->m_RespawnTick = Server()->Tick()+Server()->TickSpeed()/2;
        int ModeSpecial = GameServer()->m_pController->OnCharacterDeath(this, (Killer < 0) ? 0 : GameServer()->m_apPlayers[Killer], Weapon);

        char aBuf[256];
        if(Killer < 0)
        {
            str_format(aBuf, sizeof(aBuf), "kill killer='%d:%d:' victim='%d:%d:%s' weapon=%d special=%d",
                Killer, - 1 - Killer,
                m_pPlayer->GetCID(), m_pPlayer->GetTeam(), Server()->ClientName(m_pPlayer->GetCID()), Weapon, ModeSpecial
            );
        }
        else
        {
            str_format(aBuf, sizeof(aBuf), "kill killer='%d:%d:%s' victim='%d:%d:%s' weapon=%d special=%d",
                Killer, GameServer()->m_apPlayers[Killer]->GetTeam(), Server()->ClientName(Killer),
                m_pPlayer->GetCID(), m_pPlayer->GetTeam(), Server()->ClientName(m_pPlayer->GetCID()), Weapon, ModeSpecial
            );
        }
        GameServer()->Console()->Print(IConsole::OUTPUT_LEVEL_DEBUG, "game", aBuf);

        // send the kill message
        CNetMsg_Sv_KillMsg Msg;
        Msg.m_Victim = m_pPlayer->GetCID();
        Msg.m_ModeSpecial = ModeSpecial;
        for(int i = 0 ; i < MAX_CLIENTS; i++)
        {
            if(!Server()->ClientIngame(i))
                continue;

            if(Killer < 0 && Server()->GetClientVersion(i) < MIN_KILLMESSAGE_CLIENTVERSION)
            {
                Msg.m_Killer = 0;
                Msg.m_Weapon = WEAPON_WORLD;
            }
            else
            {
                Msg.m_Killer = Killer;
                Msg.m_Weapon = Weapon;
            }
            Server()->SendPackMsg(&Msg, MSGFLAG_VITAL, i);
        }

        // a nice sound
        GameServer()->CreateSound(m_Pos, SOUND_PLAYER_DIE);

        // this is for auto respawn after 3 secs
        m_pPlayer->m_DieTick = Server()->Tick();

        GameWorld()->RemoveEntity(this);
        GameWorld()->m_Core.m_apCharacters[m_pPlayer->GetCID()] = 0;
        GameServer()->CreateDeath(m_Pos, m_pPlayer->GetCID());
    }
                
            


The client side file that used to handle this was called killmessages.cpp and was renamed to a more generic infomessages.cpp when 0.7 introduced race related messages in the "killfeed".
                
void CInfoMessages::OnMessage(int MsgType, void *pRawMsg)
{
	// [..]

	bool Race = m_pClient->m_GameInfo.m_GameFlags&GAMEFLAG_RACE;

	if(MsgType == NETMSGTYPE_SV_KILLMSG)
	{
		// [..]

		CNetMsg_Sv_KillMsg *pMsg = (CNetMsg_Sv_KillMsg *)pRawMsg;

		// unpack messages
		CInfoMsg Kill;
		Kill.m_Player1ID = pMsg->m_Victim;
		if(Config()->m_ClShowsocial)
		{
			Kill.m_Player1NameCursor.m_FontSize = 36.0f;
			TextRender()->TextDeferred(&Kill.m_Player1NameCursor, m_pClient->m_aClients[Kill.m_Player1ID].m_aName, -1);
		}

		Kill.m_Player1RenderInfo = m_pClient->m_aClients[Kill.m_Player1ID].m_RenderInfo;

		Kill.m_Player2ID = pMsg->m_Killer;
		// [..]

		Kill.m_Weapon = pMsg->m_Weapon;
		Kill.m_ModeSpecial = pMsg->m_ModeSpecial;
		Kill.m_FlagCarrierBlue = m_pClient->m_Snap.m_pGameDataFlag ? m_pClient->m_Snap.m_pGameDataFlag->m_FlagCarrierBlue : -1;

		AddInfoMsg(INFOMSG_KILL, Kill);
	}
    // [..]
                
            
In a update of 0.7 support for the community mod race was added. So in hud.cpp the client also resets the checkpoint time of the tee that died. Which is not used by the standard gametypes.
                
    void CHud::OnMessage(int MsgType, void *pRawMsg)
    {
        // [..]
        else if(MsgType == NETMSGTYPE_SV_KILLMSG && (m_pClient->m_GameInfo.m_GameFlags&GAMEFLAG_RACE))
        {
            // reset checkpoint time on death
            CNetMsg_Sv_KillMsg *pMsg = (CNetMsg_Sv_KillMsg *)pRawMsg;
            if(pMsg->m_Victim == m_pClient->m_LocalClientID)
                m_CheckpointTime = 0;
        }
    }
                
            
The client does also process this message in CStats::OnMessage()


NETMSGTYPE_SV_TUNEPARAMS

Sender:Server
Recipient:Client
Message ID:6
Response to: TODO
Expected response:None
Flags:MSGFLAG_VITAL
Argument name Type Note
ground_control_speed Int TODO
ground_control_accel Int TODO
ground_friction Int TODO
ground_jump_impulse Int TODO
air_jump_impulse Int TODO
air_control_speed Int TODO
air_control_accel Int TODO
air_friction Int TODO
hook_length Int TODO
hook_fire_speed Int TODO
hook_drag_accel Int TODO
hook_drag_speed Int TODO
gravity Int TODO
velramp_start Int TODO
velramp_range Int TODO
velramp_curvature Int TODO
gun_curvature Int TODO
gun_speed Int TODO
gun_lifetime Int TODO
shotgun_curvature Int TODO
shotgun_speed Int TODO
shotgun_speeddiff Int TODO
shotgun_lifetime Int TODO
grenade_curvature Int TODO
grenade_speed Int TODO
grenade_lifetime Int TODO
laser_reach Int TODO
laser_bounce_delay Int TODO
laser_bounce_num Int TODO
laser_bounce_cost Int TODO
player_collision Int TODO
player_hooking Int TODO
All the fields of this message are defined in the tuning.h file.

Unpacked by the client in CGameClient::OnMessage()
                
    void CGameClient::OnMessage(int MsgId, CUnpacker *pUnpacker)
    {
        Client()->RecordGameMessage(true);

        // special messages
        if(MsgId == NETMSGTYPE_SV_TUNEPARAMS && Client()->State() != IClient::STATE_DEMOPLAYBACK)
        {
            Client()->RecordGameMessage(false);
            // unpack the new tuning
            CTuningParams NewTuning;
            int *pParams = (int *)&NewTuning;
            for(unsigned i = 0; i < sizeof(CTuningParams)/sizeof(int); i++)
                pParams[i] = pUnpacker->GetInt();

            // check for unpacking errors
            if(pUnpacker->Error())
                return;

            m_ServerMode = SERVERMODE_PURE;

            // apply new tuning
            m_Tuning = NewTuning;
            return;
        }
                
            


NETMSGTYPE_SV_EXTRAPROJECTILE

Sender:Unused (should be Server)
Recipient:Unused (should be Client)
Message ID:7
Response to: Unused
Expected response:Unused
Flags:Unused
Argument name Type Note
Unused


NETMSGTYPE_SV_READYTOENTER

Sender:Server
Recipient:Client
Message ID:8
Response to: NETMSGTYPE_CL_STARTINFO
Expected response: NETMSG_ENTERGAME
Flags: MSGFLAG_VITAL & MSGFLAG_FLUSH
Argument name Type Note
None
It is sent by the server as a response to NETMSGTYPE_CL_STARTINFO in the method CGameContext::OnMessage(int MsgID, CUnpacker *pUnpacker, int ClientID)
                
    CNetMsg_Sv_ReadyToEnter m;
    Server()->SendPackMsg(&m, MSGFLAG_VITAL|MSGFLAG_FLUSH, ClientID);
                
            
It is processed by the client in CGameClient::OnMessage(int MsgId, CUnpacker *pUnpacker)
                
    else if(MsgId == NETMSGTYPE_SV_READYTOENTER)
    {
        Client()->EnterGame();
    }
                
            
Which calls CClient::EnterGame() which then calls CClient::SendEnterGame()
                
    void CClient::SendEnterGame()
    {
        CMsgPacker Msg(NETMSG_ENTERGAME, true);
        SendMsg(&Msg, MSGFLAG_VITAL|MSGFLAG_FLUSH);
    }
                
            
Where it responds with NETMSG_ENTERGAME


NETMSGTYPE_SV_WEAPONPICKUP

Sender:Server
Recipient:Client
Message ID:9
Response to: Sent when tees touch/collect one of those weapons:
grenade, shotgun or laser
Expected response: None
Flags: MSGFLAG_VITAL
Argument name Type Note
Weapon Int TODO


NETMSGTYPE_SV_EMOTICON

Sender:Server
Recipient:Client
Message ID:10
Response to: NETMSGTYPE_CL_EMOTICON
Expected response: None
Flags: MSGFLAG_VITAL
Argument name Type Note
Client ID Int Client ID of the tee who sent the emoticon.
Emoticon Int
  • 0 - oop!
  • 1 - alert
  • 2 - heart
  • 3 - tear
  • 4 - ...
  • 5 - music
  • 6 - sorry
  • 7 - ghost
  • 8 - annoyed
  • 9 - angry
  • 10 - devil
  • 11 - swearing
  • 12 - zzZ
  • 13 - WTF
  • 14 - happy
  • 15 - ??


NETMSGTYPE_SV_VOTECLEAROPTIONS

Sender:Server
Recipient:Client
Message ID:11
Response to: NETMSGTYPE_CL_STARTINFO
Expected response: None
Flags: MSGFLAG_VITAL
Argument name Type Note
None
This message is used by the server to empty all vote entries in the client menu.


NETMSGTYPE_SV_VOTEOPTIONLISTADD

Sender:Server
Recipient:Client
Message ID:12
Response to: TODO
Expected response: None
Flags: MSGFLAG_VITAL
Argument name Type Note
Num options Int Indicates how many string fields are following.
Vote description (repeated "Num options" times) String Display name of the vote option
One of the few messages with a dynamic amount of fields. The first field is always a int determining how many string fields are following. The strings represent the descriptions of the votes that will show up in the client menu vote option list.
                
    else if(MsgId == NETMSGTYPE_SV_VOTEOPTIONLISTADD)
    {
        int NumOptions = pUnpacker->GetInt();
        for(int i = 0; i < NumOptions; i++)
        {
            const char *pDescription = pUnpacker->GetString(CUnpacker::SANITIZE_CC);
            if(pUnpacker->Error())
                return;

            m_pVoting->AddOption(pDescription);
        }
    }
                
            
Sent by the server in CGameContext::OnMessage()
                
    CVoteOptionServer *pCurrent = m_pVoteOptionFirst;
    while(pCurrent)
    {
        // count options for actual packet
        int NumOptions = 0;
        for(CVoteOptionServer *p = pCurrent; p && NumOptions < MAX_VOTE_OPTION_ADD; p = p->m_pNext, ++NumOptions);

        // pack and send vote list packet
        CMsgPacker Msg(NETMSGTYPE_SV_VOTEOPTIONLISTADD);
        Msg.AddInt(NumOptions);
        while(pCurrent && NumOptions--)
        {
            Msg.AddString(pCurrent->m_aDescription, VOTE_DESC_LENGTH);
            pCurrent = pCurrent->m_pNext;
        }
        Server()->SendMsg(&Msg, MSGFLAG_VITAL, ClientID);
    }
                
            


NETMSGTYPE_SV_VOTEOPTIONADD

Sender:Server
Recipient:Client
Message ID:13
Response to: TODO
Expected response: None
Flags: MSGFLAG_VITAL
Argument name Type Note
Vote description String Display name of the vote option
Same use case as the NETMSGTYPE_SV_VOTEOPTIONLISTADD but for single vote option entries.
                
    else if(MsgType == NETMSGTYPE_SV_VOTEOPTIONADD)
    {
        CNetMsg_Sv_VoteOptionAdd *pMsg = (CNetMsg_Sv_VoteOptionAdd *)pRawMsg;
        AddOption(pMsg->m_pDescription);
    }
                
            


NETMSGTYPE_SV_VOTEOPTIONREMOVE

Sender:Server
Recipient:Client
Message ID:14
Response to: TODO
Expected response: None
Flags: MSGFLAG_VITAL
Argument name Type Note
Vote description String Display name of the vote option to be deleted


NETMSGTYPE_SV_VOTESET

Sender:Server
Recipient:Client
Message ID:15
Response to: TODO
Expected response: TODO
Flags: MSGFLAG_VITAL
Argument name Type Note
Client ID Int TODO
Type Int
  • 0 - VOTE_UNKNOWN
  • 1 - VOTE_START_OP
  • 2 - VOTE_START_KICK
  • 3 - VOTE_START_SPEC
  • 4 - VOTE_END_ABORT
  • 5 - VOTE_END_PASS
  • 6 - VOTE_END_FAIL
Timeout Int TODO
Description String TODO
Reason String TODO
Indicates that a new vote was started. The client will render the current vote status and the server is ready to receive yes or no votes via NETMSGTYPE_CL_VOTE.


NETMSGTYPE_SV_VOTESTATUS

Sender:Server
Recipient:Client
Message ID:16
Response to: TODO
Expected response: None
Flags: MSGFLAG_VITAL
Argument name Type Note
Yes Int TODO
No Int TODO
Pass Int TODO
Total Int TODO


NETMSGTYPE_SV_SERVERSETTINGS

Sender:Server
Recipient:Client
Message ID:17
Response to: NETMSG_READY |
NETMSG_RCON_CMD
(lock_teams, sv_vote_kick, sv_vote_kick_min, sv_vote_spectate, sv_teambalance_time, sv_player_slots, sv_max_clients)
Expected response: None
Flags: MSGFLAG_VITAL
Argument name Type Note
Kick vote Int If it is set to 1 kick votes are allowed.
If it is set to 0 kick votes are not allowed.

The client shows the "Server does not allow voting to kick players" message. In the menu when the user tries to start a kick vote.

The value can be set by a server admin using the sv_vote_kick setting. If that value is updated via rcon on a running server this network message is sent to inform the clients.
Kick minimum Int Minimum amount of players needed on the server to allow kick votes to be started.
Spectator vote Int If it is set to 1 spectator votes are allowed.
If it is set to 0 spectator votes are not allowed.

The client shows the "Server does not allow voting to move players to spectators" message. In the menu when the user tries to start a move to spectator vote.

The value can be set by a server admin using the sv_vote_spectate setting. If that value is updated via rcon on a running server this network message is sent to inform the clients.
Team lock Int If it is set to 1 changing team is allowed.
If it is set to 0 changing team is not allowed.

If the teams are locked the client shows a "Teams are locked" in the menu. Where the join red/blue/spec buttons are.
Team balance Int TODO
Player slots Int This value is used to fill in the number in the "Only %d active players are allowed" string that is shown to the clients when they try to join the game but the maximum amount of in game players is already reached.

The value can be set by a server admin using the sv_player_slots setting. If that value is updated via rcon on a running server this network message is sent to inform the clients.


NETMSGTYPE_SV_CLIENTINFO

Sender:Server
Recipient:Client
Message ID:18
Response to: NETMSG_ENTERGAME
Expected response: None
Flags: MSGFLAG_VITAL & MSGFLAG_NORECORD
Argument name Type Note
Client ID Int TODO
Local Int (bool) Is set to true (1) if the client gets his own info.
Used to inform the client about his own Client ID.
Team Int TODO
Name String TODO
Clan String TODO
Country Int TODO
body String Stored in m_aaSkinPartNames[0]
marking String Stored in m_aaSkinPartNames[1]
decoration String Stored in m_aaSkinPartNames[2]
hands String Stored in m_aaSkinPartNames[3]
feet String Stored in m_aaSkinPartNames[4]
eyes String Stored in m_aaSkinPartNames[5]
Custom color body Int (bool) Stored in m_aUseCustomColors[0]
Custom color marking Int (bool) Stored in m_aUseCustomColors[1]
Custom color decoration Int (bool) Stored in m_aUseCustomColors[2]
Custom color hands Int (bool) Stored in m_aUseCustomColors[3]
Custom color feet Int (bool) Stored in m_aUseCustomColors[4]
Custom color eyes Int (bool) Stored in m_aUseCustomColors[5]
Color body Int Stored in m_aSkinPartColors[0]
Color marking Int Stored in m_aSkinPartColors[1]
Color decoration Int Stored in m_aSkinPartColors[2]
Color hands Int Stored in m_aSkinPartColors[3]
Color feet Int Stored in m_aSkinPartColors[4]
Color eyes Int Stored in m_aSkinPartColors[5]
Silent Int (bool) A int field used as a boolean. Indicating wether the client should show a client enter message in the chat or not.
Possible values are:
  • 1 - silent (no chat message)
  • 0 - not silent (enter game chat message)

What is sent in NETMSGTYPE_SV_CLIENTINFO?

In skins.cpp all the skin part names are listed in order.
                
    const char * const CSkins::ms_apSkinPartNames[NUM_SKINPARTS] = {"body", "marking", "decoration", "hands", "feet", "eyes"};
                
            
Every skin part has his own name and color. Thats why the argument list has 6 fields with all the names followed by 6 fields if that part is using a custom color or not followed by 6 fields with the corresponding custom color values.

When is NETMSGTYPE_SV_CLIENTINFO sent?

Every time a new client connects its info will be sent to all clients that were already connected. And also the info of every client that is already connected will be sent to the client that just connected.
                
    void CGameContext::OnClientEnter(int ClientID)
    {
        // [..]

        // update client infos (others before local)
        CNetMsg_Sv_ClientInfo NewClientInfoMsg;
        NewClientInfoMsg.m_ClientID = ClientID;
        NewClientInfoMsg.m_Local = 0;
        NewClientInfoMsg.m_Team = m_apPlayers[ClientID]->GetTeam();
        NewClientInfoMsg.m_pName = Server()->ClientName(ClientID);
        NewClientInfoMsg.m_pClan = Server()->ClientClan(ClientID);
        NewClientInfoMsg.m_Country = Server()->ClientCountry(ClientID);
        NewClientInfoMsg.m_Silent = false;

        if(Config()->m_SvSilentSpectatorMode && m_apPlayers[ClientID]->GetTeam() == TEAM_SPECTATORS)
            NewClientInfoMsg.m_Silent = true;

        for(int p = 0; p < NUM_SKINPARTS; p++)
        {
            NewClientInfoMsg.m_apSkinPartNames[p] = m_apPlayers[ClientID]->m_TeeInfos.m_aaSkinPartNames[p];
            NewClientInfoMsg.m_aUseCustomColors[p] = m_apPlayers[ClientID]->m_TeeInfos.m_aUseCustomColors[p];
            NewClientInfoMsg.m_aSkinPartColors[p] = m_apPlayers[ClientID]->m_TeeInfos.m_aSkinPartColors[p];
        }

        for(int i = 0; i < MAX_CLIENTS; ++i)
        {
            if(i == ClientID || !m_apPlayers[i] || (!Server()->ClientIngame(i) && !m_apPlayers[i]->IsDummy()))
                continue;

            // new info for others
            if(Server()->ClientIngame(i))
                Server()->SendPackMsg(&NewClientInfoMsg, MSGFLAG_VITAL|MSGFLAG_NORECORD, i);

            // existing infos for new player
            CNetMsg_Sv_ClientInfo ClientInfoMsg;
            ClientInfoMsg.m_ClientID = i;
            ClientInfoMsg.m_Local = 0;
            ClientInfoMsg.m_Team = m_apPlayers[i]->GetTeam();
            ClientInfoMsg.m_pName = Server()->ClientName(i);
            ClientInfoMsg.m_pClan = Server()->ClientClan(i);
            ClientInfoMsg.m_Country = Server()->ClientCountry(i);
            ClientInfoMsg.m_Silent = false;
            for(int p = 0; p < NUM_SKINPARTS; p++)
            {
                ClientInfoMsg.m_apSkinPartNames[p] = m_apPlayers[i]->m_TeeInfos.m_aaSkinPartNames[p];
                ClientInfoMsg.m_aUseCustomColors[p] = m_apPlayers[i]->m_TeeInfos.m_aUseCustomColors[p];
                ClientInfoMsg.m_aSkinPartColors[p] = m_apPlayers[i]->m_TeeInfos.m_aSkinPartColors[p];
            }
            Server()->SendPackMsg(&ClientInfoMsg, MSGFLAG_VITAL|MSGFLAG_NORECORD, ClientID);
        }

        // local info
        NewClientInfoMsg.m_Local = 1;
        Server()->SendPackMsg(&NewClientInfoMsg, MSGFLAG_VITAL|MSGFLAG_NORECORD, ClientID);

        // [..]
    }
                
            


NETMSGTYPE_SV_GAMEINFO

Sender:Server
Recipient:Client
Message ID:19
Response to: Round end |
NETMSG_RCON_CMD sv_scorelimit, sv_timelimit, sv_maprotation, sv_matches_per_map |
NETMSG_ENTERGAME
Expected response: None
Flags: MSGFLAG_VITAL & MSGFLAG_NORECORD
Argument name Type Note
Game flags Int Gameflags can have any combination of those flag bits set:
  • 1 - GAMEFLAG_TEAMS
  • 2 - GAMEFLAG_FLAGS
  • 4 - GAMEFLAG_SURVIVAL
  • 8 - GAMEFLAG_RACE
Score limit Int TODO
Time limit Int TODO
Match num Int TODO
Match current Int TODO
Sent by the server in UpdateGameInfo()
                
    void IGameController::UpdateGameInfo(int ClientID)
    {
        CNetMsg_Sv_GameInfo GameInfoMsg;
        GameInfoMsg.m_GameFlags = m_GameFlags;
        GameInfoMsg.m_ScoreLimit = m_GameInfo.m_ScoreLimit;
        GameInfoMsg.m_TimeLimit = m_GameInfo.m_TimeLimit;
        GameInfoMsg.m_MatchNum = m_GameInfo.m_MatchNum;
        GameInfoMsg.m_MatchCurrent = m_GameInfo.m_MatchCurrent;

        CNetMsg_Sv_GameInfo GameInfoMsgNoRace = GameInfoMsg;
        GameInfoMsgNoRace.m_GameFlags &= ~GAMEFLAG_RACE;

        if(ClientID == -1)
        {
            for(int i = 0; i < MAX_CLIENTS; ++i)
            {
                if(!GameServer()->m_apPlayers[i] || !Server()->ClientIngame(i))
                    continue;

                CNetMsg_Sv_GameInfo *pInfoMsg = (Server()->GetClientVersion(i) < CGameContext::MIN_RACE_CLIENTVERSION) ? &GameInfoMsgNoRace : &GameInfoMsg;
                Server()->SendPackMsg(pInfoMsg, MSGFLAG_VITAL|MSGFLAG_NORECORD, i);
            }
        }
        else
        {
            CNetMsg_Sv_GameInfo *pInfoMsg = (Server()->GetClientVersion(ClientID) < CGameContext::MIN_RACE_CLIENTVERSION) ? &GameInfoMsgNoRace : &GameInfoMsg;
            Server()->SendPackMsg(pInfoMsg, MSGFLAG_VITAL|MSGFLAG_NORECORD, ClientID);
        }
    }
                
            
Unpacked by the client in gameclient.cpp
                
    else if(MsgId == NETMSGTYPE_SV_GAMEINFO && Client()->State() != IClient::STATE_DEMOPLAYBACK)
    {
        Client()->RecordGameMessage(false);
        CNetMsg_Sv_GameInfo *pMsg = (CNetMsg_Sv_GameInfo *)pRawMsg;

        m_GameInfo.m_GameFlags = pMsg->m_GameFlags;
        m_GameInfo.m_ScoreLimit = pMsg->m_ScoreLimit;
        m_GameInfo.m_TimeLimit = pMsg->m_TimeLimit;
        m_GameInfo.m_MatchNum = pMsg->m_MatchNum;
        m_GameInfo.m_MatchCurrent = pMsg->m_MatchCurrent;
    }
                
            


NETMSGTYPE_SV_CLIENTDROP

Sender:Server
Recipient:Client
Message ID:20
Response to: Either triggered by a disconnecting client via NET_CTRLMSG_CLOSE. Or sent by the server if it disconnects a client due to kick or timeout.
Expected response: None
Flags: MSGFLAG_VITAL & MSGFLAG_NORECORD
Argument name Type Note
Client ID Int TODO
Reason String TODO
Silent Int (bool) TODO
If one client leaves the server sends this message to all remaining clients. To inform them about the updated connected player list. Which also causes the client to print the 'nameless tee' has left the game chat message. In 0.6 this used to be a regular chat message the server sent to the client. Now in 0.7 this is a network message allowing the client to localize the message locally. The client is just informed about who left the game and then translates the "left the game" string into the language the client is running in.

Sent by the server in CGameContext::OnClientDrop()
                
    void CGameContext::OnClientDrop(int ClientID, const char *pReason)
    {
        AbortVoteOnDisconnect(ClientID);
        m_pController->OnPlayerDisconnect(m_apPlayers[ClientID]);

        // update clients on drop
        if(Server()->ClientIngame(ClientID) || IsClientBot(ClientID))
        {
            if(Server()->DemoRecorder_IsRecording())
            {
                CNetMsg_De_ClientLeave Msg;
                Msg.m_pName = Server()->ClientName(ClientID);
                Msg.m_pReason = pReason;
                Server()->SendPackMsg(&Msg, MSGFLAG_NOSEND, -1);
            }

            CNetMsg_Sv_ClientDrop Msg;
            Msg.m_ClientID = ClientID;
            Msg.m_pReason = pReason;
            Msg.m_Silent = false;
            if(Config()->m_SvSilentSpectatorMode && m_apPlayers[ClientID]->GetTeam() == TEAM_SPECTATORS)
                Msg.m_Silent = true;
            Server()->SendPackMsg(&Msg, MSGFLAG_VITAL|MSGFLAG_NORECORD, -1);
        }

        // [..]
    }
                
            
Unpacked by the client in CGameClient::OnMessage()
                
    else if(MsgId == NETMSGTYPE_SV_CLIENTDROP && Client()->State() != IClient::STATE_DEMOPLAYBACK)
    {
        Client()->RecordGameMessage(false);
        CNetMsg_Sv_ClientDrop *pMsg = (CNetMsg_Sv_ClientDrop *)pRawMsg;

        if(m_LocalClientID == pMsg->m_ClientID || !m_aClients[pMsg->m_ClientID].m_Active)
        {
            if(Config()->m_Debug)
                Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "client", "invalid clientdrop");
            return;
        }

        if(!pMsg->m_Silent)
        {
            DoLeaveMessage(m_aClients[pMsg->m_ClientID].m_aName, pMsg->m_ClientID, pMsg->m_pReason);

            if(m_pDemoRecorder->IsRecording())
            {
                CNetMsg_De_ClientLeave Msg;
                Msg.m_pName = m_aClients[pMsg->m_ClientID].m_aName;
                Msg.m_ClientID = pMsg->m_ClientID;
                Msg.m_pReason = pMsg->m_pReason;
                Client()->SendPackMsg(&Msg, MSGFLAG_NOSEND | MSGFLAG_RECORD);
            }
        }

        // [..]
    }
                
            


NETMSGTYPE_SV_GAMEMSG

Sender:Server
Recipient:Client
Message ID:21
Response to: NETMSGTYPE_CL_READYCHANGE can trigger GAMEMSG_GAME_PAUSED.
NETMSGTYPE_CL_SETSPECTATORMODE can trigger GAMEMSG_SPEC_INVALIDID.
NETMSG_RCON_CMD swap_teams triggers GAMEMSG_TEAM_SWAP.
NETMSG_RCON_CMD set_team_all triggers GAMEMSG_TEAM_ALL.
The other messages depend on gameplay state and are not direct responses to net messages from the client (auto team balance, flag cap/grab/return/capture).
Expected response: None
Flags: MSGFLAG_VITAL
Argument name Type Note
Game message ID Int Can be one of those values:
  • 0 - GAMEMSG_TEAM_SWAP
  • 1 - GAMEMSG_SPEC_INVALIDID
  • 2 - GAMEMSG_TEAM_SHUFFLE
  • 3 - GAMEMSG_TEAM_BALANCE
  • 4 - GAMEMSG_CTF_DROP
  • 5 - GAMEMSG_CTF_RETURN
Argument name Type Note
Game message ID Int Can be one of those values:
  • 6 - GAMEMSG_TEAM_ALL
  • 7 - GAMEMSG_TEAM_BALANCE_VICTIM
  • 8 - GAMEMSG_CTF_GRAB
  • 10 - GAMEMSG_GAME_PAUSED
Parameter 1 Int The parameter depends on the type of game message:
  • GAMEMSG_TEAM_ALL => Team
  • GAMEMSG_TEAM_BALANCE_VICTIM => Team
  • GAMEMSG_CTF_GRAB => Team
  • GAMEMSG_GAME_PAUSED => ClientID (pause initiator)
Argument name Type Note
Game message ID Int There is only one game message with 3 parameters:
  • 9 - GAMEMSG_CTF_CAPTURE
Parameter 1 Int Flag:
  • 0 - red *** The red flag was captured by 'ChillerDragon' (0.02 seconds)
  • 1 - blue *** The blue flag was captured by 'ChillerDragon' (0.02 seconds)
Parameter 2 Int This is the ClientID of the player who captured the flag.
Parameter 3 Int This is the capture time and it will be converted to a float like This
                            
    float Time = aParaI[2] / (float)Client()->GameTickSpeed();
                            
                        
GAMEMSG_SPEC_INVALIDID and GAMEMSG_TEAM_BALANCE_VICTIM are sent to specific ClientIDs and all other game messages are broadcasted to all connected players.

The amount of parameters depends on the Game message ID The server has 3 overloaded methods to send this message with either 0, 1 or 3 parameters
                
    void CGameContext::SendGameMsg(int GameMsgID, int ClientID)
    {
        CMsgPacker Msg(NETMSGTYPE_SV_GAMEMSG);
        Msg.AddInt(GameMsgID);
        Server()->SendMsg(&Msg, MSGFLAG_VITAL, ClientID);
    }

    void CGameContext::SendGameMsg(int GameMsgID, int ParaI1, int ClientID)
    {
        CMsgPacker Msg(NETMSGTYPE_SV_GAMEMSG);
        Msg.AddInt(GameMsgID);
        Msg.AddInt(ParaI1);
        Server()->SendMsg(&Msg, MSGFLAG_VITAL, ClientID);
    }

    void CGameContext::SendGameMsg(int GameMsgID, int ParaI1, int ParaI2, int ParaI3, int ClientID)
    {
        CMsgPacker Msg(NETMSGTYPE_SV_GAMEMSG);
        Msg.AddInt(GameMsgID);
        Msg.AddInt(ParaI1);
        Msg.AddInt(ParaI2);
        Msg.AddInt(ParaI3);
        Server()->SendMsg(&Msg, MSGFLAG_VITAL, ClientID);
    }
                
            
Server and client have a shared list of pre agreed "Game message ID" to "parameter" mappings. The client unpacks the message like this:
                
    else if(MsgId == NETMSGTYPE_SV_GAMEMSG)
    {
        int GameMsgID = pUnpacker->GetInt();

        // check for valid gamemsgid
        if(GameMsgID < 0 || GameMsgID >= NUM_GAMEMSGS)
            return;

        int aParaI[3];
        int NumParaI = 0;

        // get paras
        switch(gs_GameMsgList[GameMsgID].m_ParaType)
        {
        case PARA_I: NumParaI = 1; break;
        case PARA_II: NumParaI = 2; break;
        case PARA_III: NumParaI = 3; break;
        }
        for(int i = 0; i < NumParaI; i++)
        {
            aParaI[i] = pUnpacker->GetInt();
        }

        // check for unpacking errors
        if(pUnpacker->Error())
            return;

        // handle special messages
        char aBuf[256];
        bool TeamPlay = m_GameInfo.m_GameFlags&GAMEFLAG_TEAMS;
        if(gs_GameMsgList[GameMsgID].m_Action == DO_SPECIAL)
        {
            switch(GameMsgID)
            {
            case GAMEMSG_CTF_DROP:
                m_pSounds->Enqueue(CSounds::CHN_GLOBAL, SOUND_CTF_DROP);
                break;
            case GAMEMSG_CTF_RETURN:
                m_pSounds->Enqueue(CSounds::CHN_GLOBAL, SOUND_CTF_RETURN);
                break;
            case GAMEMSG_TEAM_ALL:
                {
                    const char *pMsg = "";
                    switch(GetStrTeam(aParaI[0], TeamPlay))
                    {
                    case STR_TEAM_GAME: pMsg = Localize("All players were moved to the game"); break;
                    case STR_TEAM_RED: pMsg = Localize("All players were moved to the red team"); break;
                    case STR_TEAM_BLUE: pMsg = Localize("All players were moved to the blue team"); break;
                    case STR_TEAM_SPECTATORS: pMsg = Localize("All players were moved to the spectators"); break;
                    }
                    m_pBroadcast->DoClientBroadcast(pMsg);
                }
                break;
            case GAMEMSG_TEAM_BALANCE_VICTIM:
                {
                    const char *pMsg = "";
                    switch(GetStrTeam(aParaI[0], TeamPlay))
                    {
                    case STR_TEAM_RED: pMsg = Localize("You were moved to the red team due to team balancing"); break;
                    case STR_TEAM_BLUE: pMsg = Localize("You were moved to the blue team due to team balancing"); break;
                    }
                    m_pBroadcast->DoClientBroadcast(pMsg);
                }
                break;
            case GAMEMSG_CTF_GRAB:
                if(m_LocalClientID != -1 && (m_aClients[m_LocalClientID].m_Team != aParaI[0] || (m_Snap.m_SpecInfo.m_Active &&
                                ((m_Snap.m_SpecInfo.m_SpectatorID != -1 && m_aClients[m_Snap.m_SpecInfo.m_SpectatorID].m_Team != aParaI[0]) ||
                                (m_Snap.m_SpecInfo.m_SpecMode == SPEC_FLAGRED && aParaI[0] != TEAM_RED) ||
                                (m_Snap.m_SpecInfo.m_SpecMode == SPEC_FLAGBLUE && aParaI[0] != TEAM_BLUE)))))
                    m_pSounds->Enqueue(CSounds::CHN_GLOBAL, SOUND_CTF_GRAB_PL);
                else
                    m_pSounds->Enqueue(CSounds::CHN_GLOBAL, SOUND_CTF_GRAB_EN);
                break;
            case GAMEMSG_GAME_PAUSED:
                {
                    int ClientID = clamp(aParaI[0], 0, MAX_CLIENTS - 1);
                    char aLabel[64];
                    GetPlayerLabel(aLabel, sizeof(aLabel), ClientID, m_aClients[ClientID].m_aName);
                    str_format(aBuf, sizeof(aBuf), Localize("'%s' initiated a pause"), aLabel);
                    m_pChat->AddLine(aBuf);
                }
                break;
            case GAMEMSG_CTF_CAPTURE:
                m_pSounds->Enqueue(CSounds::CHN_GLOBAL, SOUND_CTF_CAPTURE);
                int ClientID = clamp(aParaI[1], 0, MAX_CLIENTS - 1);
                m_pStats->OnFlagCapture(ClientID);
                char aLabel[64];
                GetPlayerLabel(aLabel, sizeof(aLabel), ClientID, m_aClients[ClientID].m_aName);

                float Time = aParaI[2] / (float)Client()->GameTickSpeed();
                if(Time <= 60)
                {
                    if(aParaI[0])
                    {
                        str_format(aBuf, sizeof(aBuf), Localize("The blue flag was captured by '%s' (%.2f seconds)"), aLabel, Time);
                    }
                    else
                    {
                        str_format(aBuf, sizeof(aBuf), Localize("The red flag was captured by '%s' (%.2f seconds)"), aLabel, Time);
                    }
                }
                else
                {
                    if(aParaI[0])
                    {
                        str_format(aBuf, sizeof(aBuf), Localize("The blue flag was captured by '%s'"), aLabel);
                    }
                    else
                    {
                        str_format(aBuf, sizeof(aBuf), Localize("The red flag was captured by '%s'"), aLabel);
                    }
                }
                m_pChat->AddLine(aBuf);
            }
            return;
        }

        // build message
        const char *pText = "";
        if(NumParaI == 0)
        {
            pText = Localize(gs_GameMsgList[GameMsgID].m_pText);
        }

        // handle message
        switch(gs_GameMsgList[GameMsgID].m_Action)
        {
        case DO_CHAT:
            m_pChat->AddLine(pText);
            break;
        case DO_BROADCAST:
            m_pBroadcast->DoClientBroadcast(pText);
            break;
        }
    }
                
            


NETMSGTYPE_DE_CLIENTENTER

Sender:[ Client ] (not send over the network just used for demos)
Recipient:[ Client ] (not send over the network just used for demos)
Message ID:22
Response to: NETMSGTYPE_SV_CLIENTINFO
Expected response: None
Flags: MSGFLAG_NOSEND & MSGFLAG_RECORD
Argument name Type Note
Name String TODO
Client ID Int TODO
Team Int TODO
This message is only used when the client is recording a demo. There is no udp packet being sent to the server or anywhere else. This is just added by the client to the demo file to be later read from the demo file by the client again.

The client basically translates the NETMSGTYPE_SV_CLIENTINFO message to this NETMSGTYPE_DE_CLIENTENTER message. Note that it only happens when the client is recording a demo if(m_pDemoRecorder->IsRecording())
                
    if(MsgId == NETMSGTYPE_SV_CLIENTINFO && Client()->State() != IClient::STATE_DEMOPLAYBACK)
    {
        // [..]

        if(pMsg->m_Local)
        {
            // [..]
        }
        else
        {
            // [..]

            if(m_LocalClientID != -1 && !pMsg->m_Silent)
            {
                if(m_pDemoRecorder->IsRecording())
                {
                    CNetMsg_De_ClientEnter Msg;
                    Msg.m_pName = pMsg->m_pName;
                    Msg.m_ClientID = pMsg->m_ClientID;
                    Msg.m_Team = pMsg->m_Team;
                    Client()->SendPackMsg(&Msg, MSGFLAG_NOSEND|MSGFLAG_RECORD);
                }
            }
        }
    }
                
            
And then the client only unpacks this message when in demo playback mode Client()->State() == IClient::STATE_DEMOPLAYBACK
                
    else if(MsgId == NETMSGTYPE_DE_CLIENTENTER && Client()->State() == IClient::STATE_DEMOPLAYBACK)
    {
        CNetMsg_De_ClientEnter *pMsg = (CNetMsg_De_ClientEnter *)pRawMsg;
        DoEnterMessage(pMsg->m_pName, pMsg->m_ClientID, pMsg->m_Team);
        m_pStats->OnPlayerEnter(pMsg->m_ClientID, pMsg->m_Team);
    }
                
            


NETMSGTYPE_DE_CLIENTLEAVE

Sender:[ Client, Server] (not send over the network just used for demos)
Recipient:[ Client ] (not send over the network just used for demos)
Message ID:23
Response to: TODO
Expected response: None
Flags: MSGFLAG_NOSEND & MSGFLAG_RECORD
Argument name Type Note
Name String TODO
Client ID Int TODO
Reason String TODO


NETMSGTYPE_CL_SAY

Sender:Client
Recipient:Server
Message ID:24
Response to: TODO
Expected response: TODO
Flags: TODO
Argument name Type Note
Mode Int Chat mode can be one of those
  • 1 - CHAT_ALL
  • 2 - CHAT_TEAM
  • 3 - CHAT_WHISPER
Target Int This is the client ID of the receiving party. It should be set to -1 if it is not a whisper message.
Message String Message that will be printed in chat. The server trims right and cuts off after 128 utf8-characters.


NETMSGTYPE_CL_SETTEAM

Sender:Client
Recipient:Server
Message ID:25
Response to: The client running the console command team or clicking one of the team join buttons in the menu.
Expected response: NETMSGTYPE_SV_TEAM (Only on success. If the team is full, there is a ratelimit or any other error the server will not respond)
Flags: MSGFLAG_VITAL
Argument name Type Note
Team Int Can be one of those:
  • -1 - TEAM_SPECTATORS
  • 0 - TEAM_RED
  • 1 - TEAM_BLUE
Unpacked by the server in gamecontext.cpp
                
    else if(MsgID == NETMSGTYPE_CL_SETTEAM && m_pController->IsTeamChangeAllowed())
    {
            CNetMsg_Cl_SetTeam *pMsg = (CNetMsg_Cl_SetTeam *)pRawMsg;

            if(pPlayer->GetTeam() == pMsg->m_Team ||
                    (Config()->m_SvSpamprotection && pPlayer->m_LastSetTeamTick && pPlayer->m_LastSetTeamTick+Server()->TickSpeed()*3 > Server()->Tick()) ||
                    (pMsg->m_Team != TEAM_SPECTATORS && m_LockTeams) || pPlayer->m_TeamChangeTick > Server()->Tick())
                    return;

            pPlayer->m_LastSetTeamTick = Server()->Tick();

            // Switch team on given client and kill/respawn him
            if(m_pController->CanJoinTeam(pMsg->m_Team, ClientID) && m_pController->CanChangeTeam(pPlayer, pMsg->m_Team))
            {
                    if(pPlayer->GetTeam() == TEAM_SPECTATORS || pMsg->m_Team == TEAM_SPECTATORS)
                            m_VoteUpdate = true;
                    pPlayer->m_TeamChangeTick = Server()->Tick()+Server()->TickSpeed()*3;
                    m_pController->DoTeamChange(pPlayer, pMsg->m_Team);
            }
    }
                
            
The server never responds with an error message. In case of failed team change the server will silently drop the team change request. The client is supposed to do the error handling locally. Because it knows about all the cases where team changes will fail. So the client deactivates the menu buttons locally if a team change is impossible. This allows the client to fully translate the error messages. So the client keeps track of the server settings like locked teams and in game slots. And it also has to check if the teams are balanced by counting the amount of team members locally before doing a team change request.


NETMSGTYPE_CL_SETSPECTATORMODE

Sender:Client
Recipient:Server
Message ID:26
Response to: TODO
Expected response: NETMSGTYPE_SV_GAMEMSG (GAMEMSG_SPEC_INVALIDID)
Flags: MSGFLAG_VITAL
Argument name Type Note
Mode Int The value should be one of those
  • 0 - SPEC_FREEVIEW
  • 1 - SPEC_PLAYER
  • 2 - SPEC_FLAGRED
  • 3 - SPEC_FLAGBLUE
Spectator ID Int Should be either the ClientId when the mode is SPEC_PLAYER or -1 in all other modes.
On success the server will update the snap item obj_spectator_info. On error the server will respond with NETMSGTYPE_SV_GAMEMSG (GAMEMSG_SPEC_INVALIDID).

Sent by the client if the spectate next or spectate previous bind is triggerd. Or if the client user picks a player in the specate menu. Or selects free view in the spectate menu. Or if the user "clicks" on a player in free view.
In src/game/client/components/spectator.cpp there is a Spectate() method used by the client to send this message.
                
    void CSpectator::Spectate(int SpecMode, int SpectatorID)
    {
        if(Client()->State() == IClient::STATE_DEMOPLAYBACK)
        {
            m_pClient->m_DemoSpecMode = clamp(SpecMode, 0, NUM_SPECMODES-1);
            m_pClient->m_DemoSpecID = clamp(SpectatorID, -1, MAX_CLIENTS-1);
            return;
        }

        if(m_pClient->m_Snap.m_SpecInfo.m_SpecMode == SpecMode && (SpecMode != SPEC_PLAYER || m_pClient->m_Snap.m_SpecInfo.m_SpectatorID == SpectatorID))
            return;

        CNetMsg_Cl_SetSpectatorMode Msg;
        Msg.m_SpecMode = SpecMode;
        Msg.m_SpectatorID = SpectatorID;
        Client()->SendPackMsg(&Msg, MSGFLAG_VITAL);
    }
                
            
Unpacked by the server in gamecontext.cpp
                
    else if (MsgID == NETMSGTYPE_CL_SETSPECTATORMODE && !m_World.m_Paused)
    {
            CNetMsg_Cl_SetSpectatorMode *pMsg = (CNetMsg_Cl_SetSpectatorMode *)pRawMsg;

            if(Config()->m_SvSpamprotection && pPlayer->m_LastSetSpectatorModeTick && pPlayer->m_LastSetSpectatorModeTick+Server()->TickSpeed() > Server()->Tick())
                    return;

            pPlayer->m_LastSetSpectatorModeTick = Server()->Tick();
            if(!pPlayer->SetSpectatorID(pMsg->m_SpecMode, pMsg->m_SpectatorID))
                    SendGameMsg(GAMEMSG_SPEC_INVALIDID, ClientID);
    }
                
            


NETMSGTYPE_CL_STARTINFO

Sender:Client
Recipient:Server
Message ID:27
Response to: NETMSG_CON_READY
Expected response: NETMSGTYPE_SV_VOTECLEAROPTIONS &
NETMSGTYPE_SV_READYTOENTER
Flags: MSGFLAG_VITAL & MSGFLAG_FLUSH
Argument name Type Note
Name String TODO
Clan String TODO
Country Int TODO
body String Stored in m_aaSkinPartNames[0]
marking String Stored in m_aaSkinPartNames[1]
decoration String Stored in m_aaSkinPartNames[2]
hands String Stored in m_aaSkinPartNames[3]
feet String Stored in m_aaSkinPartNames[4]
eyes String Stored in m_aaSkinPartNames[5]
Custom color body Int (bool) Stored in m_aUseCustomColors[0]
Custom color marking Int (bool) Stored in m_aUseCustomColors[1]
Custom color decoration Int (bool) Stored in m_aUseCustomColors[2]
Custom color hands Int (bool) Stored in m_aUseCustomColors[3]
Custom color feet Int (bool) Stored in m_aUseCustomColors[4]
Custom color eyes Int (bool) Stored in m_aUseCustomColors[5]
Color body Int Stored in m_aSkinPartColors[0]
Color marking Int Stored in m_aSkinPartColors[1]
Color decoration Int Stored in m_aSkinPartColors[2]
Color hands Int Stored in m_aSkinPartColors[3]
Color feet Int Stored in m_aSkinPartColors[4]
Color eyes Int Stored in m_aSkinPartColors[5]
Sent by the client in SendStartInfo()
                
    void CGameClient::SendStartInfo()
    {
        CNetMsg_Cl_StartInfo Msg;
        Msg.m_pName = Config()->m_PlayerName;
        Msg.m_pClan = Config()->m_PlayerClan;
        Msg.m_Country = Config()->m_PlayerCountry;
        for(int p = 0; p < NUM_SKINPARTS; p++)
        {
            Msg.m_apSkinPartNames[p] = CSkins::ms_apSkinVariables[p];
            Msg.m_aUseCustomColors[p] = *CSkins::ms_apUCCVariables[p];
            Msg.m_aSkinPartColors[p] = *CSkins::ms_apColorVariables[p];
        }
        Client()->SendPackMsg(&Msg, MSGFLAG_VITAL|MSGFLAG_FLUSH);
    }
                
            
Which is called in OnConnected()
                
    void CGameClient::OnConnected()
    {
        m_Layers.Init(Kernel());
        m_Collision.Init(Layers());

        for(int i = 0; i < m_All.m_Num; i++)
        {
            m_All.m_paComponents[i]->OnMapLoad();
            m_All.m_paComponents[i]->OnReset();
        }

        m_ServerMode = SERVERMODE_PURE;

        // send the inital info
        SendStartInfo();
    }
                
            
Unpacked by the server in CGameContext::OnMessage(int MsgID, CUnpacker *pUnpacker, int ClientID)


NETMSGTYPE_CL_KILL

Sender:Client
Recipient:Server
Message ID:28
Response to: Client console command kill
Expected response: NETMSGTYPE_SV_KILLMSG (WEAPON_SELF) & event_sound_world (SOUND_PLAYER_DIE=16) & event_death
Flags: MSGFLAG_VITAL
Requests selfkill. The server might block it. Processed by the server in gamecontext.cpp
                
    else if (MsgID == NETMSGTYPE_CL_KILL && !m_World.m_Paused)
    {
            if(pPlayer->m_LastKillTick && pPlayer->m_LastKillTick+Server()->TickSpeed()*3 > Server()->Tick())
                    return;

            pPlayer->m_LastKillTick = Server()->Tick();
            pPlayer->KillCharacter(WEAPON_SELF);
    }
                
            


NETMSGTYPE_CL_READYCHANGE

Sender:Client
Recipient:Server
Message ID:29
Response to: TODO
Expected response: TODO
Flags:MSGFLAG_VITAL
Message to request game pause and unpause. Will pause the game for everyone if one person sends it and if the server allows it. Will unpause the game if everyone send it.

Needs sv_player_ready_mode set to 1 on the server side.
                
    void CGameClient::SendReadyChange()
    {
        CNetMsg_Cl_ReadyChange Msg;
        Client()->SendPackMsg(&Msg, MSGFLAG_VITAL);
    }
                
            
                
    else if (MsgID == NETMSGTYPE_CL_READYCHANGE)
    {
        if(pPlayer->m_LastReadyChangeTick && pPlayer->m_LastReadyChangeTick+Server()->TickSpeed()*1 > Server()->Tick())
            return;

        pPlayer->m_LastReadyChangeTick = Server()->Tick();
        m_pController->OnPlayerReadyChange(pPlayer);
    }
                
            


NETMSGTYPE_CL_EMOTICON

Sender:Client
Recipient:Server
Message ID:30
Response to: User picking emote in emote wheel or using the local console command emote (emote id)
Expected response: NETMSGTYPE_SV_EMOTICON
Flags:MSGFLAG_VITAL
Argument name Type Note
Emoticon Int
  • 0 - oop!
  • 1 - alert
  • 2 - heart
  • 3 - tear
  • 4 - ...
  • 5 - music
  • 6 - sorry
  • 7 - ghost
  • 8 - annoyed
  • 9 - angry
  • 10 - devil
  • 11 - swearing
  • 12 - zzZ
  • 13 - WTF
  • 14 - happy
  • 15 - ??
                
    void CEmoticon::Emote(int Emoticon)
    {
        CNetMsg_Cl_Emoticon Msg;
        Msg.m_Emoticon = Emoticon;
        Client()->SendPackMsg(&Msg, MSGFLAG_VITAL);
    }
                
            
                
    else if (MsgID == NETMSGTYPE_CL_EMOTICON && !m_World.m_Paused)
    {
        CNetMsg_Cl_Emoticon *pMsg = (CNetMsg_Cl_Emoticon *)pRawMsg;

        if(Config()->m_SvSpamprotection && pPlayer->m_LastEmoteTick && pPlayer->m_LastEmoteTick+Server()->TickSpeed()*3 > Server()->Tick())
            return;

        pPlayer->m_LastEmoteTick = Server()->Tick();

        SendEmoticon(ClientID, pMsg->m_Emoticon);
    }
                
            


NETMSGTYPE_CL_VOTE

Sender:Client
Recipient:Server
Message ID:31
Response to: TODO
Expected response: TODO
Flags:MSGFLAG_VITAL
Argument name Type Note
Vote Int The value can be one of those
  • -1 - VOTE_CHOICE_NO
  • (0 - VOTE_CHOICE_PASS) server internal only
  • 1 - VOTE_CHOICE_YES
                
    void CVoting::Vote(int Choice)
    {
        CNetMsg_Cl_Vote Msg = { Choice };
        Client()->SendPackMsg(&Msg, MSGFLAG_VITAL);
    }
                
            
                
    else if(MsgID == NETMSGTYPE_CL_VOTE)
    {
        if(!m_VoteCloseTime)
            return;

        if(pPlayer->m_Vote == VOTE_CHOICE_PASS)
        {
            CNetMsg_Cl_Vote *pMsg = (CNetMsg_Cl_Vote *)pRawMsg;
            if(pMsg->m_Vote == VOTE_CHOICE_PASS)
                return;

            pPlayer->m_Vote = pMsg->m_Vote;
            pPlayer->m_VotePos = ++m_VotePos;
            m_VoteUpdate = true;
        }
        else if(m_VoteCreator == pPlayer->GetCID())
        {
            CNetMsg_Cl_Vote *pMsg = (CNetMsg_Cl_Vote *)pRawMsg;
            if(pMsg->m_Vote != VOTE_CHOICE_NO || m_VoteCancelTime<time_get())
                return;

            m_VoteCloseTime = -1;
        }
    }
                
            


NETMSGTYPE_CL_CALLVOTE

Sender:Client
Recipient:Server
Message ID:32
Response to: TODO
Expected response: TODO
Flags:MSGFLAG_VITAL
Argument name Type Note
Type String TODO
Value String TODO
Reason String TODO
Force Int TODO
Sent by the client via Callvote()
                
    void CVoting::Callvote(const char *pType, const char *pValue, const char *pReason, bool ForceVote)
    {
        CNetMsg_Cl_CallVote Msg = {0};
        Msg.m_Type = pType;
        Msg.m_Value = pValue;
        Msg.m_Reason = pReason;
        Msg.m_Force = ForceVote;
        Client()->SendPackMsg(&Msg, MSGFLAG_VITAL);
    }
                
            
Unpacked by the server in CGameContext::OnMessage(int MsgID, CUnpacker *pUnpacker, int ClientID)


NETMSGTYPE_SV_SKINCHANGE

Sender:Server
Recipient:Client
Message ID:33
Response to: TODO
Expected response: TODO
Flags: MSGFLAG_VITAL & MSGFLAG_NORECORD
Argument name Type Note
Client ID Int TODO
body String Stored in m_aaSkinPartNames[0]
marking String Stored in m_aaSkinPartNames[1]
decoration String Stored in m_aaSkinPartNames[2]
hands String Stored in m_aaSkinPartNames[3]
feet String Stored in m_aaSkinPartNames[4]
eyes String Stored in m_aaSkinPartNames[5]
Custom color body Int (bool) Stored in m_aUseCustomColors[0]
Custom color marking Int (bool) Stored in m_aUseCustomColors[1]
Custom color decoration Int (bool) Stored in m_aUseCustomColors[2]
Custom color hands Int (bool) Stored in m_aUseCustomColors[3]
Custom color feet Int (bool) Stored in m_aUseCustomColors[4]
Custom color eyes Int (bool) Stored in m_aUseCustomColors[5]
Color body Int Stored in m_aSkinPartColors[0]
Color marking Int Stored in m_aSkinPartColors[1]
Color decoration Int Stored in m_aSkinPartColors[2]
Color hands Int Stored in m_aSkinPartColors[3]
Color feet Int Stored in m_aSkinPartColors[4]
Color eyes Int Stored in m_aSkinPartColors[5]
                
    void CGameContext::SendSkinChange(int ClientID, int TargetID)
    {
        CNetMsg_Sv_SkinChange Msg;
        Msg.m_ClientID = ClientID;
        for(int p = 0; p < NUM_SKINPARTS; p++)
        {
            Msg.m_apSkinPartNames[p] = m_apPlayers[ClientID]->m_TeeInfos.m_aaSkinPartNames[p];
            Msg.m_aUseCustomColors[p] = m_apPlayers[ClientID]->m_TeeInfos.m_aUseCustomColors[p];
            Msg.m_aSkinPartColors[p] = m_apPlayers[ClientID]->m_TeeInfos.m_aSkinPartColors[p];
        }
        Server()->SendPackMsg(&Msg, MSGFLAG_VITAL|MSGFLAG_NORECORD, TargetID);
    }
                
            
                
    else if(MsgId == NETMSGTYPE_SV_SKINCHANGE && Client()->State() != IClient::STATE_DEMOPLAYBACK)
	{
		Client()->RecordGameMessage(false);
		CNetMsg_Sv_SkinChange *pMsg = (CNetMsg_Sv_SkinChange *)pRawMsg;

		if(!m_aClients[pMsg->m_ClientID].m_Active)
		{
			if(Config()->m_Debug)
				Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "client", "invalid skin info");
			return;
		}

		for(int i = 0; i < NUM_SKINPARTS; i++)
		{
			str_utf8_copy_num(m_aClients[pMsg->m_ClientID].m_aaSkinPartNames[i], pMsg->m_apSkinPartNames[i], sizeof(m_aClients[pMsg->m_ClientID].m_aaSkinPartNames[i]), MAX_SKIN_LENGTH);
			m_aClients[pMsg->m_ClientID].m_aUseCustomColors[i] = pMsg->m_aUseCustomColors[i];
			m_aClients[pMsg->m_ClientID].m_aSkinPartColors[i] = pMsg->m_aSkinPartColors[i];
		}
		m_aClients[pMsg->m_ClientID].UpdateRenderInfo(this, pMsg->m_ClientID, true);
	}
                
            


NETMSGTYPE_CL_SKINCHANGE

Sender:Client
Recipient:Server
Message ID:34
Response to: TODO
Expected response: TODO
Flags: MSGFLAG_VITAL & MSGFLAG_FLUSH & MSGFLAG_NORECORD
Argument name Type Note
body String Stored in m_aaSkinPartNames[0]
marking String Stored in m_aaSkinPartNames[1]
decoration String Stored in m_aaSkinPartNames[2]
hands String Stored in m_aaSkinPartNames[3]
feet String Stored in m_aaSkinPartNames[4]
eyes String Stored in m_aaSkinPartNames[5]
Custom color body Int (bool) Stored in m_aUseCustomColors[0]
Custom color marking Int (bool) Stored in m_aUseCustomColors[1]
Custom color decoration Int (bool) Stored in m_aUseCustomColors[2]
Custom color hands Int (bool) Stored in m_aUseCustomColors[3]
Custom color feet Int (bool) Stored in m_aUseCustomColors[4]
Custom color eyes Int (bool) Stored in m_aUseCustomColors[5]
Color body Int Stored in m_aSkinPartColors[0]
Color marking Int Stored in m_aSkinPartColors[1]
Color decoration Int Stored in m_aSkinPartColors[2]
Color hands Int Stored in m_aSkinPartColors[3]
Color feet Int Stored in m_aSkinPartColors[4]
Color eyes Int Stored in m_aSkinPartColors[5]
                
    void CGameClient::SendSkinChange()
    {
        CNetMsg_Cl_SkinChange Msg;
        for(int p = 0; p < NUM_SKINPARTS; p++)
        {
            Msg.m_apSkinPartNames[p] = CSkins::ms_apSkinVariables[p];
            Msg.m_aUseCustomColors[p] = *CSkins::ms_apUCCVariables[p];
            Msg.m_aSkinPartColors[p] = *CSkins::ms_apColorVariables[p];
        }
        Client()->SendPackMsg(&Msg, MSGFLAG_VITAL|MSGFLAG_NORECORD|MSGFLAG_FLUSH);
        m_LastSkinChangeTime = Client()->LocalTime();
    }
                
            
                
    else if(MsgID == NETMSGTYPE_CL_SKINCHANGE)
    {
        if(pPlayer->m_LastChangeInfoTick && pPlayer->m_LastChangeInfoTick+Server()->TickSpeed()*5 > Server()->Tick())
            return;

        pPlayer->m_LastChangeInfoTick = Server()->Tick();
        CNetMsg_Cl_SkinChange *pMsg = (CNetMsg_Cl_SkinChange *)pRawMsg;

        for(int p = 0; p < NUM_SKINPARTS; p++)
        {
            str_utf8_copy_num(pPlayer->m_TeeInfos.m_aaSkinPartNames[p], pMsg->m_apSkinPartNames[p], sizeof(pPlayer->m_TeeInfos.m_aaSkinPartNames[p]), MAX_SKIN_LENGTH);
            pPlayer->m_TeeInfos.m_aUseCustomColors[p] = pMsg->m_aUseCustomColors[p];
            pPlayer->m_TeeInfos.m_aSkinPartColors[p] = pMsg->m_aSkinPartColors[p];
        }

        // update all clients
        for(int i = 0; i < MAX_CLIENTS; ++i)
        {
            if(!m_apPlayers[i] || (!Server()->ClientIngame(i) && !m_apPlayers[i]->IsDummy()) || Server()->GetClientVersion(i) < MIN_SKINCHANGE_CLIENTVERSION)
                continue;

            SendSkinChange(pPlayer->GetCID(), i);
        }

        m_pController->OnPlayerInfoChange(pPlayer);
    }
                
            


NETMSGTYPE_SV_RACEFINISH

Sender:[Server] (vanilla servers do not send it)
Recipient:Client
Message ID:35
Response to: Decided by mod implementations. Should indicate that a tee finished the race.
Expected response: None
Flags: Unused
Argument name Type Note
Client ID Int TODO
Time Int TODO
Diff Int TODO
Record personal Int TODO
Record server Int TODO
                
    else if(MsgType == NETMSGTYPE_SV_RACEFINISH && Race)
    {
        CNetMsg_Sv_RaceFinish *pMsg = (CNetMsg_Sv_RaceFinish *)pRawMsg;

        char aBuf[256];
        char aTime[32];
        char aLabel[64];

        FormatTime(aTime, sizeof(aTime), pMsg->m_Time, m_pClient->RacePrecision());
        m_pClient->GetPlayerLabel(aLabel, sizeof(aLabel), pMsg->m_ClientID, m_pClient->m_aClients[pMsg->m_ClientID].m_aName);

        str_format(aBuf, sizeof(aBuf), "%2d: %s: finished in %s", pMsg->m_ClientID, m_pClient->m_aClients[pMsg->m_ClientID].m_aName, aTime);
        Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "race", aBuf);

        if(pMsg->m_RecordPersonal || pMsg->m_RecordServer)
        {
            if(pMsg->m_RecordServer)
                str_format(aBuf, sizeof(aBuf), Localize("'%s' has set a new map record: %s"), aLabel, aTime);
            else // m_RecordPersonal
                str_format(aBuf, sizeof(aBuf), Localize("'%s' has set a new personal record: %s"), aLabel, aTime);

            if(pMsg->m_Diff < 0)
            {
                char aImprovement[64];
                char aDiff[32];
                FormatTimeDiff(aDiff, sizeof(aDiff), absolute(pMsg->m_Diff), m_pClient->RacePrecision(), false);
                str_format(aImprovement, sizeof(aImprovement), Localize(" (%s seconds faster)"), aDiff);
                str_append(aBuf, aImprovement, sizeof(aBuf));
            }

            m_pClient->m_pChat->AddLine(aBuf);
        }

        if(m_pClient->m_Snap.m_pGameDataRace && m_pClient->m_Snap.m_pGameDataRace->m_RaceFlags&RACEFLAG_FINISHMSG_AS_CHAT)
        {
            if(!pMsg->m_RecordPersonal && !pMsg->m_RecordServer) // don't print the time twice
            {
                str_format(aBuf, sizeof(aBuf), Localize("'%s' finished in: %s"), aLabel, aTime);
                m_pClient->m_pChat->AddLine(aBuf);
            }
        }
        else
        {
            CInfoMsg Finish;
            Finish.m_Player1ID = pMsg->m_ClientID;
            Finish.m_Player1RenderInfo = m_pClient->m_aClients[Finish.m_Player1ID].m_RenderInfo;

            Finish.m_TimeCursor.m_FontSize = 36.0f;
            if(pMsg->m_RecordServer)
                TextRender()->TextColor(1.0f, 0.5f, 0.0f, 1.0f);
            else if(pMsg->m_RecordPersonal)
                TextRender()->TextColor(0.2f, 0.6f, 1.0f, 1.0f);
            TextRender()->TextDeferred(&Finish.m_TimeCursor, aTime, -1);

            if(Config()->m_ClShowsocial)
            {
                Finish.m_Player1NameCursor.m_FontSize = 36.0f;
                TextRender()->TextDeferred(&Finish.m_Player1NameCursor, m_pClient->m_aClients[pMsg->m_ClientID].m_aName, -1);
            }

            FormatTimeDiff(aTime, sizeof(aTime), pMsg->m_Diff, m_pClient->RacePrecision());
            str_format(aBuf, sizeof(aBuf), "(%s)", aTime);
            Finish.m_DiffCursor.m_FontSize = 36.0f;
            if(pMsg->m_Diff < 0)
                TextRender()->TextColor(0.5f, 1.0f, 0.5f, 1.0f);
            else
                TextRender()->TextColor(1.0f, 0.5f, 0.5f, 1.0f);
            TextRender()->TextDeferred(&Finish.m_DiffCursor, aBuf, -1);
            TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f);

            Finish.m_Time = pMsg->m_Time;
            Finish.m_Diff = pMsg->m_Diff;
            Finish.m_RecordPersonal = pMsg->m_RecordPersonal;
            Finish.m_RecordServer = pMsg->m_RecordServer;

            AddInfoMsg(INFOMSG_FINISH, Finish);
        }
    }
                
            


NETMSGTYPE_SV_CHECKPOINT

Sender:[Server] (vanilla servers do not send it)
Recipient:Client
Message ID:36
Response to: Decided by mod implementations. Should indicate that a tee reached a race checkpoint.
Expected response: None
Flags: Unused
Argument name Type Note
Diff Int TODO
                
    void CHud::OnMessage(int MsgType, void *pRawMsg)
    {
        if(MsgType == NETMSGTYPE_SV_CHECKPOINT)
        {
            CNetMsg_Sv_Checkpoint *pMsg = (CNetMsg_Sv_Checkpoint *)pRawMsg;
            m_CheckpointDiff = pMsg->m_Diff;
            m_CheckpointTime = time_get();
        }
        // [..]
    }
                
            


NETMSGTYPE_SV_COMMANDINFO

Sender:Server
Recipient:Client
Message ID:37
Response to: TODO
Expected response:None
Flags:MSGFLAG_VITAL
Argument name Type Note
Name String Name of the command. If a user types /commandname in the chat. The command will be run.
Args format String The argument format specifies how many and what type of arguments this chat command takes.
Valid types are:
  • s - string word split
  • r - string consume till the end
  • i - integer
And then a leading ? can indicate if it is a optional argument. And [details] following the type can give more details on what the value should be.
An example for a say command could be:
r[message]
Help text String Command description displayed to the user when autocompleting chat commands.
This can be seen as the chat equivalent to the rcon counter part NETMSG_RCON_CMD_ADD
                
    void CChat::OnMessage(int MsgType, void *pRawMsg)
    {
        // [..]
        else if(MsgType == NETMSGTYPE_SV_COMMANDINFO)
        {
            CNetMsg_Sv_CommandInfo *pMsg = (CNetMsg_Sv_CommandInfo *)pRawMsg;
            if(!m_CommandManager.AddCommand(pMsg->m_Name, pMsg->m_HelpText, pMsg->m_ArgsFormat, ServerCommandCallback, this))
                dbg_msg("chat_commands", "adding server chat command: name='%s' args='%s' help='%s'", pMsg->m_Name, pMsg->m_ArgsFormat, pMsg->m_HelpText);
            else
                dbg_msg("chat_commands", "failed to add command '%s'", pMsg->m_Name);

        }
        // [..]
    }
                
            


NETMSGTYPE_SV_COMMANDINFOREMOVE

Sender:Server
Recipient:Client
Message ID:38
Response to: TODO
Expected response:None
Flags:MSGFLAG_VITAL
Argument name Type Note
Name String Name of the command to be deleted from the clients autocompletion list.
                
    void CChat::OnMessage(int MsgType, void *pRawMsg)
    {
        // [..]
        else if(MsgType == NETMSGTYPE_SV_COMMANDINFOREMOVE)
        {
            CNetMsg_Sv_CommandInfoRemove *pMsg = (CNetMsg_Sv_CommandInfoRemove *)pRawMsg;

            if(!m_CommandManager.RemoveCommand(pMsg->m_Name))
            {
                dbg_msg("chat_commands", "removed chat command: name='%s'", pMsg->m_Name);
            }
        }
    }
                
            


NETMSGTYPE_CL_COMMAND

Sender:Client
Recipient:Server
Message ID:39
Response to: Client typing a command into chat.
Expected response:Depends on command
Flags:MSGFLAG_VITAL
Argument name Type Note
Name String Name of the command to be executed.
Arguments String TODO
The client fills in command and arguments and sends it via the ServerCommandCallback() method
                
    void CChat::ServerCommandCallback(IConsole::IResult *pResult, void *pContext)
    {
        CCommandManager::SCommandContext *pComContext = (CCommandManager::SCommandContext *)pContext;
        CChat *pChatData = (CChat *)pComContext->m_pContext;

        CNetMsg_Cl_Command Msg;
        Msg.m_Name = pComContext->m_pCommand;
        Msg.m_Arguments = pComContext->m_pArgs;
        pChatData->Client()->SendPackMsg(&Msg, MSGFLAG_VITAL);

        pChatData->m_pHistoryEntry = 0x0;
        pChatData->Disable();
        pChatData->m_pClient->OnRelease();
    }
                
            
The server then calls the right callback method matching the command name
                
    else if (MsgID == NETMSGTYPE_CL_COMMAND)
    {
        CNetMsg_Cl_Command *pMsg = (CNetMsg_Cl_Command*)pRawMsg;
        CommandManager()->OnCommand(pMsg->m_Name, pMsg->m_Arguments, ClientID);
    }