Custom Networking
Anomaly handles encryption, routing, replay protection, and version checks for you. Mod authors define an IAnomalyMessage, register it on both sides, and send it through AnomalyMessaging.
Do not parse or depend on Anomaly’s internal transport format.
The contract
Section titled “The contract”Every custom message implements IAnomalyMessage from Anomaly.Shared.Networking:
using System.IO;using Anomaly.Shared.Networking;
namespace MyMod.Shared.Networking.Messages;
public class GreetingMessage : IAnomalyMessage{ public string MessageName => "mymod:greeting"; public MessageChannel TransportChannel => MessageChannel.ReliableOrdered;
public string Text { get; set; } = ""; public int Value { get; set; }
public void Serialize(BinaryWriter writer) { writer.Write(Text ?? ""); writer.Write(Value); }
public void Deserialize(BinaryReader reader) { Text = reader.ReadString(); Value = reader.ReadInt32(); }}Four things matter:
MessageNameis a string inyourmod:nameformat.TransportChannelchooses delivery behavior.Serializewrites fields in a stable order.Deserializereads fields in the same order.
Shared assembly
Section titled “Shared assembly”Put custom message classes in a project that both client and server reference, usually MyMod.Shared. Keep it free of Unity, Mirror, LabAPI, and MelonLoader references.
If you need game-specific types such as vectors, serialize primitive values in the shared message and convert on each side.
Registering the message
Section titled “Registering the message”Register the same name and factory on both sides after Anomaly has initialized.
// Client side, during OnInitializeMelonusing Anomaly.Shared.Networking;
AnomalyMessageRegistry.Register( "mymod:greeting", () => new GreetingMessage());// Server side, during plugin Enable()using Anomaly.Shared.Networking;
AnomalyMessageRegistry.Register( "mymod:greeting", () => new GreetingMessage());Sending
Section titled “Sending”Client to server
Section titled “Client to server”using Anomaly.Client.Api.Networking;
AnomalyMessaging.Send(new GreetingMessage{ Text = "hello", Value = 42});Server to one client
Section titled “Server to one client”using Anomaly.Server.Networking;
AnomalyMessaging.SendToClient(hub, new GreetingMessage{ Text = "world", Value = 7});Server to all clients
Section titled “Server to all clients”AnomalyMessaging.SendToAll(new GreetingMessage { Text = "broadcast" });Server to all clients except one
Section titled “Server to all clients except one”AnomalyMessaging.SendToAllExcept(senderHub.connectionToClient, msg);Receiving
Section titled “Receiving”Client side
Section titled “Client side”AnomalyMessaging.MessageReceived += msg =>{ if (msg is GreetingMessage g) MelonLogger.Msg($"Got greeting: {g.Text} ({g.Value})");};Or use the event system:
NetworkMessageEvents.AnomalyMessageReceived += ev =>{ if (ev.Message is GreetingMessage g) { /* ... */ }};Server side
Section titled “Server side”AnomalyMessaging.MessageReceived += (senderHub, msg) =>{ if (msg is GreetingMessage g) Logger.Info($"Received greeting: {g.Text}");};The server receives the sender’s ReferenceHub, so you can associate the message with the player using normal server APIs.
Transport channels
Section titled “Transport channels”Pick the channel based on the kind of data you send.
| Channel | Use for |
|---|---|
ReliableOrdered | Commands, purchases, settings, and one-shot state changes. |
Unreliable | High-frequency data where occasional loss is acceptable. |
Sequenced | Frequent state where only the newest value matters. |
ReliableSequenced | Reliable state where stale intermediate updates should be skipped. |
ReliableUnordered | Reliable independent messages that do not need ordering. |
For most low-frequency mod features, start with ReliableOrdered.
Bandwidth-sensitive values
Section titled “Bandwidth-sensitive values”Keep frequent messages small. Send only the fields the receiver needs, avoid large strings in per-frame updates, and prefer Sequenced for state that is immediately superseded by newer state.
Debugging
Section titled “Debugging”- Registered but not receiving. Check the opposite side registers the same message name.
- Deserialize throws. Confirm fields are read in exactly the same order they were written.
- Version mismatch. Client and server Anomaly releases are incompatible; update the mismatched side.
- Message name conflict. Rename your message with a unique mod id prefix.