User messages are the primary method for sending custom data from the server to clients in Source 2. They use Protocol Buffers (protobuf) for serialization, providing a structured and efficient way to transmit game information.
Common use cases include:
Displaying HUD elements and notifications
Playing sounds or visual effects on clients
Sending custom game state information
Creating chat messages and hints
Triggering client-side events
User messages work similarly to SourceMod's implementation, with protobuf replacing the old bitbuffer system for better type safety and flexibility.
You can intercept user messages before they're sent to clients:
c#
c++
python
go
js
lua
using Plugify;
using static s2sdk.s2sdk;
public unsafe class Sample : Plugin
{
public void OnPluginStart()
{
// Find message ID by name
short msgId = UserMessageFindMessageIdByName("TextMsg");
// Hook the message in Post mode
HookUserMessage(msgId, OnTextMessage, HookMode.Post);
}
public void OnPluginEnd()
{
short msgId = UserMessageFindMessageIdByName("TextMsg");
UnhookUserMessage(msgId, OnTextMessage, HookMode.Post);
}
private static void OnTextMessage(nint userMessage)
{
// Read message data
string text = PbReadString(userMessage, "text", 0);
PrintToServer($"TextMsg intercepted: {text}\n");
}
}
local plugify = require 'plugify'
local Plugin = plugify.Plugin
local s2 = require 's2sdk'
local Sample = {}
setmetatable(Sample, { __index = Plugin })
function Sample:on_text_message(user_message)
local text = s2:PbReadString(user_message, "text", 0)
s2:PrintToServer(string.format("TextMsg intercepted: %s\n", text))
return s2:ResultType.Continue
end
function Sample:plugin_start()
local msg_id = s2:UserMessageFindMessageIdByName("TextMsg")
s2:HookUserMessage(msg_id, function(msg) self:on_text_message(msg) end, s2.HookMode.Post)
end
function Sample:plugin_end()
local msg_id = s2:UserMessageFindMessageIdByName("TextMsg")
s2:UnhookUserMessage(msg_id, function(msg) self:on_text_message(msg) end, s2.HookMode.Post)
end
local M = {}
M.Sample = Sample
return M
// 1. Create the message
nint msg = UserMessageCreateFromName("TextMsg");
// 2. Set recipients (choose one approach)
UserMessageAddAllPlayers(msg); // All players
// OR
UserMessageAddRecipient(msg, playerSlot); // Single player
// OR
UserMessageSetRecipientMask(msg, customMask); // Custom mask
// 3. Set message fields
PbSetString(msg, "text", "Hello from server!"); // if non repeated
PbSetInt32(msg, "msg_dst", 4); // HUD_PRINTTALK
// 4. Send the message
UserMessageSend(msg);
// 5. Clean up
UserMessageDestroy(msg);
Some protobuf fields are arrays (repeated fields):
// Get count of repeated field
int count = UserMessageGetRepeatedFieldCount(msg, "items");
// Read repeated values
for (int i = 0; i < count; i++)
{
int value = PbReadInt32(msg, "items", i); // Note: index is 3rd parameter
PrintToServer($"Item[{i}] = {value}\n");
}
// Add values to repeated field
PbAddInt32(msg, "items", 10);
PbAddInt32(msg, "items", 20);
PbAddString(msg, "names", "Player1");
// Set specific index in repeated field
PbSetRepeatedInt32(msg, "items", 0, 999); // Set index 0 to 999
// Remove value from repeated field
UserMessageRemoveRepeatedFieldValue(msg, "items", 0); // Remove index 0
// Send to all players
UserMessageAddAllPlayers(msg);
// Send to specific player
UserMessageAddRecipient(msg, playerSlot);
// Send to multiple specific players
UserMessageAddRecipient(msg, 0); // Player slot 0
UserMessageAddRecipient(msg, 3); // Player slot 3
UserMessageAddRecipient(msg, 7); // Player slot 7
// Use recipient mask (advanced)
ulong mask = 0x1F; // Binary mask for first 5 players
UserMessageSetRecipientMask(msg, mask);
// Get current recipient mask
ulong currentMask = UserMessageGetRecipientMask(msg);
using System.Numerics;
using Plugify;
using static s2sdk.s2sdk;
public unsafe class FadeEffect : Plugin
{
public void OnPluginStart()
{
AddConsoleCommand("fade", "Apply screen fade effect",
ConVarFlag.LinkedConcommand | ConVarFlag.Release,
Command_Fade, HookMode.Post);
}
public ResultType Command_Fade(int caller, int context, string[] arguments)
{
if (caller == -1) return ResultType.Handled;
// Fade to red
FadeScreen(caller, 2000, 1000, 255, 0, 0, 128);
return ResultType.Handled;
}
private void FadeScreen(int playerSlot, int duration, int holdTime,
int r, int g, int b, int alpha)
{
nint msg = UserMessageCreateFromName("Fade");
// Send to specific player
UserMessageAddRecipient(msg, playerSlot);
// Set fade parameters
PbSetInt32(msg, "duration", duration); // Fade duration in ms
PbSetInt32(msg, "hold_time", holdTime); // Hold time in ms
PbSetInt32(msg, "flags", 0x0001); // FFADE_IN
// Set RGBA color
int colorPacked = (r << 24) | (g << 16) | (b << 8) | alpha;
PbSetColor(msg, "clr", colorPacked);
// Send and cleanup
UserMessageSend(msg);
UserMessageDestroy(msg);
PrintToServer("Screen fade applied\n");
}
}