Modding:Network API

From Vintage Story Wiki
Revision as of 08:44, 13 April 2020 by Tylermcwilliams (talk | contribs)


Introduction

In this example mod we will show you how to send custom data back and forth between the Server and the Client. To do this, we will set up a network channel through which string messages will be sent through a network channel.

We will create a server command that will send out a message to all clients, and if a client receives this, it will send a response back to the server; upon receiving this response, the server will display it along with the responding client's player name.

ProtoContracts

VintageStory uses the ProtoBuf library to serialize classes declared with the ProtoContract attribute. ProtoBuf is beyond the scope of this tutorial, but you can read more about this powerful library here.

[ProtoContract(ImplicitFields = ImplicitFields.AllPublic)]
    public class NetworkApiTestMessage
    {
        public string message;
    }

    [ProtoContract(ImplicitFields = ImplicitFields.AllPublic)]
    public class NetworkApiTestResponse
    {
        public string response;
    }

The fields in these classes hold the data we wish to send through our channel. We'll use NetworkApiTestMessage to encase the initial message from the server, while NetWorkApiTestResponse will be used by the Client to send its response.

Next, we'll set up our network channel on the server side, and register a server command to dispatch our message.

Server Setup

IServerNetworkChannel serverChannel;
        ICoreServerAPI serverApi;

        public override void StartServerSide(ICoreServerAPI api)
        {
            serverApi = api;

            serverChannel =
                api.Network.RegisterChannel("networkapitest")
                .RegisterMessageType(typeof(NetworkApiTestMessage))
                .RegisterMessageType(typeof(NetworkApiTestResponse))
                .SetMessageHandler<NetworkApiTestResponse>(OnClientMessage)
            ;

            api.RegisterCommand("nwtest", "Send a test network message", "", OnNwTestCmd, Privilege.controlserver);
        }

The Core Server API contains the Network member, which provides us with the RegisterChannel method. We pass this method a string containing the name we choose for our channel, in this case we'll call our channel "networkapitest". A channel with this exact name will be registered on the Client side.

We then register the ProtoContract classes that we'll be using to this channel with the RegisterMessageType method.

Finally, we call SetMessageHandler<T>, which takes a delegate that will be called every time a ProtoContract of type T is received from a client, in this case NetworkApiTestResponse will be handled by OnClientMessage which we'll declare later.

Now that our channel is set up on the Server side, we'll want to register a server command that will broadcast a message to all the clients listening to our "networkapitest" channel.

private void OnNwTestCmd(IServerPlayer player, int groupId, CmdArgs args)
        {
            serverChannel.BroadcastPacket(new NetworkApiTestMessage()
            {
                message = "Hello World!",
            });
        }

The server channel gives us the BroadcastPacket method which we'll use to send out a NetworkApiTestMessage instance to all clients listening to the "networkapitest" channel, in this case we send "Hello World!" as the message.

Client Setup

IClientNetworkChannel clientChannel;
        ICoreClientAPI clientApi;

        public override void StartClientSide(ICoreClientAPI api)
        {
            clientApi = api;

            clientChannel =
                api.Network.RegisterChannel("networkapitest")
                .RegisterMessageType(typeof(NetworkApiTestMessage))
                .RegisterMessageType(typeof(NetworkApiTestResponse))
                .SetMessageHandler<NetworkApiTestMessage>(OnServerMessage)
            ;
        }

On the Client side, we set up the "networkapitest" channel much in the same way as we did on the Server side. The IClientNetworkChannel type is the client side equivalent of the IServerNetworkChannel, and shares much of the same functionality.

This time we call SetMessageHandler to listen for messages of type NetworkApiTestMessage, using a delegate named OnServerMessage.

private void OnServerMessage(NetworkApiTestMessage networkMessage)
        {
            clientApi.ShowChatMessage("Received following message from server: " + networkMessage.message);
            clientApi.ShowChatMessage("Sending response.");
            clientChannel.SendPacket(new NetworkApiTestResponse()
            {
                response = "RE: Hello World!"
            });
        }

Here we simply show a chat message to the client with the contents of the message sent from the server.

We then let the client know that we're sending our response to the server, and call SendPacket passing it an instance of NetworkApiTestResponse.

Now let's go back to the Server side and create the handler for this response!

Handling Client's Response

private void OnClientMessage(IPlayer fromPlayer, NetworkApiTestResponse networkMessage)
        {
            serverApi.SendMessageToGroup(
                GlobalConstants.GeneralChatGroup,
                "Received following response from " + fromPlayer.PlayerName + ": " + networkMessage.response,
                EnumChatType.Notification
            );
        }

Messages handlers on the server have an additional parameter compared to client handlers, which is the player whose client sent the message.

In this handler we simply broadcast a server-wide message containing the responding player's name and the content of the response.

Final words

You now understand how to set up custom network communication between client and server!

Please note that despite the ease of making your own custom network channel, it may be more appropriate to use the prebuilt network methods in the cases of Entity and BlockEntity packets. For more information on this, check out the IServerNetworkAPI and IClientNetworkAPI.

Happy coding!