Skip to content

Sending a Packet from the Server to the Client

Valk edited this page Feb 3, 2023 · 1 revision

Sending a packet from the server to a client is much like sending a packet from a client to the server but there are a few differences.

  • Instead of CPacket it is SPacket
  • Instead of APacketClient it is APacketServer
  • Instead of void Handle() it is async Task Handle() (this may change later on)
  • Instead of Handle() being executed server-side, it is executed client-side

Lets create the packet

// Sometimes it is helpful to create a secondary opcode so we are not clogging up the main opcode pipeline
// Since the first opcode 'ServerPacketOpcode' is sent as a byte, there can not be more than 255 server opcodes
public enum ServerGameInfo
{
    PlayerJoinLeave,
    PlayersOnServer,
    StartLevel,
    MapPosition,
    PeerId
}

public class SPacketGameInfo : APacketServer
{
    public ServerGameInfo ServerGameInfo { get; set; }

    // Comments are used to organize properties as this packet is a multi-purpose packet
    // Only one ServerGameInfo value can be specified at a time

    // PlayerJoinLeave
    public string Username { get; set; }
    public bool Joining { get; set; }
    public byte Id { get; set; }

    // PlayersOnServer
    public Dictionary<byte, string> Usernames { get; set; }

    // StartLevel
    public string LevelName { get; set; }

    // MapPosition
    public Vector2 MapPosition { get; set; }

    // PeerId
    public byte PeerId { get; set; }

    public override void Write(PacketWriter writer)
    {
        writer.Write((ushort)ServerGameInfo);

        // Handle each opcode accordingly 
        switch (ServerGameInfo)
        {
            case ServerGameInfo.PlayerJoinLeave:
                writer.Write(Username);
                writer.Write(Joining);
                writer.Write((byte)Id);
                break;
            case ServerGameInfo.PlayersOnServer:
                writer.Write((byte)Usernames.Count);
                foreach (var player in Usernames) 
                {
                    writer.Write((byte)player.Key);
                    writer.Write(player.Value);
                }
                break;
            case ServerGameInfo.StartLevel:
                writer.Write(LevelName);
                break;
            case ServerGameInfo.MapPosition:
                writer.Write(MapPosition);
                break;
            case ServerGameInfo.PeerId:
                writer.Write((byte)PeerId);
                break;
        }
    }

    public override void Read(PacketReader reader)
    {
        ServerGameInfo = (ServerGameInfo)reader.ReadUShort();

        // notice Logger.Log(...) is not used directly here because Client.Log gives a nice prefix and color automatically
        Net.Client.Log($"Received: {ServerGameInfo}");

        switch (ServerGameInfo)
        {
            case ServerGameInfo.PlayerJoinLeave:
                Username = reader.ReadString();
                Joining = reader.ReadBool();
                Id = reader.ReadByte();
                break;
            case ServerGameInfo.PlayersOnServer:
                var length = reader.ReadByte();
                Usernames = new Dictionary<byte, string>();
                for (int i = 0; i < length; i++) 
                {
                    var key = reader.ReadByte();
                    var value = reader.ReadString();

                    Usernames.Add(key, value);
                }
                break;
            case ServerGameInfo.StartLevel:
                LevelName = reader.ReadString();
                break;
            case ServerGameInfo.MapPosition:
                MapPosition = reader.ReadVector2();
                break;
            case ServerGameInfo.PeerId:
                PeerId = reader.ReadByte();
                break;
        }
    }

    public override async Task Handle()
    {
        // if there are no uses of await keyword in the Handle() method then add 'await Task.FromResult(0);' at the very bottom
        switch (ServerGameInfo)
        {
            case ServerGameInfo.PlayerJoinLeave:
                HandlePlayerJoinLeave();
                break;
            case ServerGameInfo.PlayersOnServer:
                HandlePlayersOnServer();
                break;
            case ServerGameInfo.StartLevel:
                await HandleStartLevel();
                break;
            case ServerGameInfo.MapPosition:
                HandleMapPosition();
                break;
            case ServerGameInfo.PeerId:
                HandlePeerId();
                break;
        }
    }

    // ...
}

Send the server packet to all clients but the host

Net.Server.SendToEveryoneButHost(ServerPacketOpcode.GameInfo, new SPacketGameInfo
{
        ServerGameInfo = ServerGameInfo.StartLevel,
	LevelName = LevelManager.CurrentLevel
});

Send the server packet to a specific client specified by peer

server.Send(ServerPacketOpcode.GameInfo, new SPacketGameInfo
{
    ServerGameInfo = ServerGameInfo.PlayersOnServer,
    Usernames = server.Players.ToDictionary(x => x.Key, x => x.Value.Username)
}, peer);

Send the server packet to everyone but a specific client specified by peer ID

SendToOtherPlayers(netEvent.Peer.ID, ServerPacketOpcode.GameInfo, new SPacketGameInfo
{
    ServerGameInfo = ServerGameInfo.PlayerJoinLeave,
    Username = username,
    Joining = false,
    Id = (byte)netEvent.Peer.ID
});
Clone this wiki locally