Network Messages
SwiftlyS2's typed net-message API sends generated protobuf messages and intercepts them in both client-to-server and server-to-client directions, through APIISwiftlyCore.NetMessage (APIINetMessageService).
Net-message objects passed to callbacks are temporary wrappers. Read or copy values inside the callback and don't store those objects for later use.
Sending Net Messages
APIINetMessageService.Send builds the message, runs your configure callback, and sends it via the configured recipient filter - use it for one-off sends:
Core.NetMessage.Send<CUserMessageShake>(msg =>
{
msg.Duration = 1.0f;
msg.Frequency = 2.0f;
msg.Amplitude = 0.5f;
msg.Command = 0;
msg.Recipients.AddAllPlayers();
});Use APIINetMessageService.Create instead to build the same message shape repeatedly, tweak recipients directly through APIINetMessage`1.Recipients (a APICRecipientFilter), and send with Send()/SendToPlayer/SendToAllPlayers:
using var msg = Core.NetMessage.Create<CUserMessageShake>();
msg.Duration = 1.0f;
msg.Frequency = 2.0f;
msg.Amplitude = 0.5f;
msg.Command = 0;
msg.Recipients.RemoveAllPlayers();
msg.Recipients.AddRecipient(0);
msg.Recipients.AddRecipient(1);
msg.Send(); // or msg.SendToPlayer(0) / msg.SendToAllPlayers()Hooking Net Messages
Three hook pipelines: client messages (APIINetMessageService.HookClientMessage, client-to-server), server messages (APIINetMessageService.HookServerMessage, server-to-client), and internal server messages (APIINetMessageService.HookServerMessageInternal, server-to-client with a target playerId - only a small subset of server-to-client messages use this pipeline). Each can be registered programmatically or via an attribute discovered on any registered class (your BasePlugin is registered by default).
Client Hooks
private Guid _clientMoveHook;
public override void Load(bool hotReload)
{
_clientMoveHook = Core.NetMessage.HookClientMessage<CCLCMsg_Move>(OnClientMove);
}
public override void Unload()
{
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;
}Server and Internal Server Hooks
Same shape, different attribute and delegate signature - APIServerNetMessageHandler has no playerId, APIServerNetMessageInternalHandler does:
[ServerNetMessageHandler]
public HookResult OnServerSound(CMsgSosStartSoundEvent msg)
{
Console.WriteLine($"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;
}Register the programmatic equivalents with APIINetMessageService.HookServerMessage/APIINetMessageService.HookServerMessageInternal, same Load/Unload/Guid pattern as the client hook above.
HookResult Behavior
Net-message hooks return HookResult.Continue (allow) or HookResult.Stop (block) - in client hooks that blocks delivery to the server, in server/internal hooks it blocks delivery to clients.
Unhooking
Unhook by Guid via APIINetMessageService.Unhook, 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>();See APIINetMessageService.UnhookClientMessage, APIINetMessageService.UnhookServerMessage and APIINetMessageService.UnhookServerMessageInternal. Their handler delegates are generic over T constrained to APIITypedProtobuf`1, APIINetMessage`1 and IDisposable.
Working with the Raw Payload Accessor
Generated message interfaces expose typed properties, plus Accessor (APIIProtobufAccessor) for field-based access when needed:
private HookResult OnServerSound(CMsgSosStartSoundEvent msg)
{
uint hash = msg.Accessor.HasField("soundevent_hash")
? msg.Accessor.GetUInt32("soundevent_hash")
: msg.SoundeventHash;
Console.WriteLine($"sound hash={hash}");
return HookResult.Continue;
}