Net Messages

What network and user messages are in Source 2, how they are identified by id and by name, and how Source2Mod lets you send and hook them.

Everything a CS2 server and its clients say to each other is a protobuf message: the chat line, the screen shake, a sound starting, a voice packet, the player's movement input. Each one is a message type from Valve's .proto files plus a numeric message id. Source2Mod lets you build and send them, and sit on the wire in both directions to read, change or drop them.

Kinds of messages

prefixdirectionid enum (.proto file)message classexamples
net_both ways, engine levelNET_Messages (networkbasetypes.proto)CNETMsg_*net_Tick, net_StringCmd, net_SetConVar
clc_client → serverCLC_Messages (netmessages.proto)CCLCMsg_*clc_Move, clc_VoiceData, clc_RespondCvarValue
svc_server → clientSVC_Messages (netmessages.proto)CSVCMsg_*svc_Print, svc_VoiceData, svc_GetCvarValue
UM_server → client, user messagesEBaseUserMessages (usermessages.proto)CUserMessage*UM_Fade, UM_Shake, UM_TextMsg, UM_SayText2
CS_UM_server → client, CS2 user messagesECstrike15UserMessages (cstrike15_usermessages.proto)CCSUsrMsg_*CS_UM_HintText, CS_UM_ServerRankRevealAll
GE_server → client, game eventsEBaseGameEvents (gameevents.proto), ECsgoGameEvents (cs_gameevents.proto)CMsg*GE_SosStartSoundEvent, GE_FireBulletsId
TE_server → client, temp entitiesETEProtobufIds (te.proto)CMsgTE*TE_EffectDispatchId, TE_ExplosionId

User messages are what most plugins want: short server-to-client messages that drive the HUD and effects — text in chat or the centre of the screen, screen fades and shakes, hint boxes. The net_ / clc_ / svc_ messages are the engine's own protocol; you mostly hook those rather than send them.

Source2Mod's game events API (Events) is a different thing: IGameEvent events like player_death. The GE_ protobuf messages above are network messages the engine happens to call "game events" (sounds, decals, bullets).

Ids and names

Every message type is registered with the engine's INetworkMessages under both an id and the name of its protobuf class. Source2Mod takes either:

  • By id — the value from the .proto enum, e.g. UM_Shake = 120, CS_UM_HintText = 323, clc_VoiceData = 22. The generated headers give them to you as plain enums: include usermessages.pb.h, cstrike15_usermessages.pb.h, netmessages.pb.h, … (generated into your build by the SDK). The SDK also has them as enum class types under source2mod/schema/entity/enums/ (EBaseUserMessages, ECstrike15UserMessages, SVC_Messages, CLC_Messages, …).
  • By name — the protobuf class name, e.g. "CUserMessageShake", "CCSUsrMsg_HintText", "CSVCMsg_GetCvarValue". The lookup is a partial match: "Shake" also works, but it matches CUserMessageShake, CUserMessageShakeDir and CCSUsrMsg_Shake alike, and you get whichever the engine finds first. Use the full class name.

The id is what everything else keys on: sending needs it, and hooks report it. Allocating by name is a convenience for when you do not have the enum at hand — you still pass the id when you send.

CS2 carries two families side by side: UM_Shake (CUserMessageShake, field amplitude) and CS_UM_Shake (CCSUsrMsg_Shake, field local_amplitude), UM_Fade and CS_UM_Fade, and so on. Their fields differ. Pick one, and fill the fields of that message — field names are looked up on the message you allocated, and a name it does not have is silently ignored.

Fields

A message's fields are exactly those in its .proto definition. Source2Mod reads and writes them by name through protobuf reflection, so you do not need the generated class — only the field names and types from the .proto:

usermessages.proto
message CUserMessageShake {
	optional uint32 command = 1;
	optional float amplitude = 2;
	optional float frequency = 3;
	optional float duration = 4;
}

→ SetUInt32(msg, "command", 0), SetFloat(msg, "amplitude", 10.0f), … The .proto files for CS2 are published in the SteamTracking/Protobufs repository.

How messages travel

The server sends every message through IGameEventSystem::PostEventAbstract, with a recipient set of player slots. Messages from clients arrive per client and pass through the engine's pre-filter (INetworkMessageProcessingPreFilter::FilterMessage) before the server handles them. The s2m hooks both places once and hands every plugin a callback:

APIsees
NET_MSG_SEND / NET_MSG_SEND_PLAYERS— sends through PostEventAbstract
HOOK_SERVER_MESSAGEeverything posted through PostEventAbstract, with its recipients
HOOK_CLIENT_MESSAGEeverything a client sends to the server
HOOK_SERVER_INTERNAL_MESSAGEmessages as they go out on one client's channel

All of it is on ISource2ModNetworkMessages, reached through g_pSource2ModNetworkMessages once SOURCE2MOD_SAVEVARS() has run. Reference: ISource2ModNetworkMessages. Scripts call the same methods on Source2ModNetworkMessages (source2mod/netmessages.inc), with the ids from source2mod/netmessage_ids.inc.

On this page