Network Messages
SwiftlyS2 provides a typed net-message API for sending generated protobuf messages and intercepting them in both client->server and server->client directions.
Net-message objects used in callbacks are temporary wrappers. Read or copy values inside the callback and do not store those objects for later use.
Accessing Net Message Service
Net-message APIs are available through Core.NetMessage.
public override void Load(bool hotReload)
{
var netMessages = Core.NetMessage;
}Sending Net Messages
One-Off Send
Use Send<T> for short, one-off messages.
Core.NetMessage.Send<CUserMessageShake>(msg =>
{
msg.Duration = 1.0f;
msg.Frequency = 2.0f;
msg.Amplitude = 0.5f;
msg.Command = 0;
msg.Recipients.AddAllPlayers();
});Send<T> creates the message, runs your configure callback, and sends it using the configured recipient filter.
Reusing a Message Instance
Use Create<T> when you want to build and send the same message shape repeatedly.
using var msg = Core.NetMessage.Create<CUserMessageShake>();
msg.Duration = 1.0f;
msg.Frequency = 2.0f;
msg.Amplitude = 0.5f;
msg.Command = 0;
msg.SendToAllPlayers();
msg.SendToPlayer(0);Recipient Filters
INetMessage<T>.Recipients exposes CRecipientFilter so you can build custom audiences.
using var msg = Core.NetMessage.Create<CUserMessageShake>();
msg.Recipients.RemoveAllPlayers();
msg.Recipients.AddRecipient(0);
msg.Recipients.AddRecipient(1);
msg.Send();Hooking Net Messages
SwiftlyS2 supports three net-message hook pipelines:
- Client messages (
HookClientMessage<T>) for client->server traffic - Server messages (
HookServerMessage<T>) for server->client traffic - Internal server messages (
HookServerMessageInternal<T>) for server->client traffic with targetplayerId
Client Hooks
private Guid _clientMoveHook;
public override void Load(bool hotReload)
{
_clientMoveHook = Core.NetMessage.HookClientMessage<CCLCMsg_Move>(OnClientMove);
}
public override void Unload(bool hotReload)
{
if (_clientMoveHook != Guid.Empty)
{
Core.NetMessage.Unhook(_clientMoveHook);
}
}
private HookResult OnClientMove(CCLCMsg_Move msg, int playerId)
{
Console.WriteLine($"player={playerId}, lastCmd={msg.LastCommandNumber}");
return HookResult.Continue;
}[ClientNetMessageHandler]
public HookResult OnClientMove(CCLCMsg_Move msg, int playerId)
{
Console.WriteLine($"player={playerId}, lastCmd={msg.LastCommandNumber}");
return HookResult.Continue;
}Server Hooks
private Guid _serverSoundHook;
public override void Load(bool hotReload)
{
_serverSoundHook = Core.NetMessage.HookServerMessage<CMsgSosStartSoundEvent>(OnServerSound);
}
private HookResult OnServerSound(CMsgSosStartSoundEvent msg)
{
Console.WriteLine($"sound hash={msg.SoundeventHash}");
return HookResult.Continue;
}[ServerNetMessageHandler]
public HookResult OnServerSound(CMsgSosStartSoundEvent msg)
{
Console.WriteLine($"sound hash={msg.SoundeventHash}");
return HookResult.Continue;
}Internal Server Hooks
Internal hooks include a playerId argument and are useful when behavior depends on the exact receiving player.
Internal hooks are not used for every message sent from server to client, but only to a small subset of them.
private Guid _serverInternalHook;
public override void Load(bool hotReload)
{
_serverInternalHook = Core.NetMessage.HookServerMessageInternal<CMsgSosStartSoundEvent>(OnServerSoundInternal);
}
private HookResult OnServerSoundInternal(CMsgSosStartSoundEvent msg, int playerId)
{
Console.WriteLine($"player={playerId}, sound hash={msg.SoundeventHash}");
return HookResult.Continue;
}[ServerNetMessageInternalHandler]
public HookResult OnServerSoundInternal(CMsgSosStartSoundEvent msg, int playerId)
{
Console.WriteLine($"player={playerId}, sound hash={msg.SoundeventHash}");
return HookResult.Continue;
}Attribute handlers are discovered on registered classes. Your main BasePlugin class is registered by default.
HookResult Behavior
Net-message hooks return HookResult:
HookResult.Continue: continue processing and allow message flowHookResult.Stop: block the message in that pipeline
Typical behavior:
- In client hooks,
Stopprevents the message from reaching the server. - In server and internal server hooks,
Stopprevents delivery to clients.
Unhooking
You can unhook by Guid or clear all hooks for a specific net-message type.
Core.NetMessage.Unhook(hookGuid);
Core.NetMessage.UnhookClientMessage<CCLCMsg_Move>();
Core.NetMessage.UnhookServerMessage<CMsgSosStartSoundEvent>();
Core.NetMessage.UnhookServerMessageInternal<CMsgSosStartSoundEvent>();Callback Signatures
Programmatic hooks use these delegate signatures:
public delegate HookResult INetMessageService.ClientNetMessageHandler<T>(T msg, int playerId)
where T : ITypedProtobuf<T>, INetMessage<T>, IDisposable;
public delegate HookResult INetMessageService.ServerNetMessageHandler<T>(T msg)
where T : ITypedProtobuf<T>, INetMessage<T>, IDisposable;
public delegate HookResult INetMessageService.ServerNetMessageInternalHandler<T>(T msg, int playerId)
where T : ITypedProtobuf<T>, INetMessage<T>, IDisposable;Working with Raw Payload Accessor
Generated message interfaces expose typed properties and Accessor (IProtobufAccessor) for field-based access when needed.
private HookResult OnServerSound(CMsgSosStartSoundEvent msg)
{
uint hash = msg.SoundeventHash;
if (msg.Accessor.HasField("soundevent_hash"))
{
hash = msg.Accessor.GetUInt32("soundevent_hash");
}
Console.WriteLine($"sound hash={hash}");
return HookResult.Continue;
}Reference
See INetMessageService for send/create/hook/unhook APIs.
See INetMessage<T> and ITypedProtobuf<T> for common message members.
See IProtobufAccessor for raw protobuf field access.
See ClientNetMessageHandler, ServerNetMessageHandler, and ServerNetMessageInternalHandler for attribute-based hooks.
See ProtobufDefinitions for generated message interfaces.