Modding:Network API: Difference between revisions

From Vintage Story Wiki
No edit summary
 
(30 intermediate revisions by 9 users not shown)
Line 1: Line 1:
__FORCETOC__
__FORCETOC__
{{GameVersion|1.19.8}}
<languages/><translate>
<!--T:1-->
Before starting, you should have a development environment set up. If you don't have one already you should read the tutorial [[Setting up your Development Environment]]. Furthermore, we assume that you have a basic understanding of the C# language and Object Oriented Programming. Let's get started!


Before starting, you should have a development environment set up. If you don't have one already you should read the tutorial [[Setting up your Development Environment]]. Furthermore, we assume that you have a basic understanding of the C# language and Object Oriented Programming. Let's get started!
<!--T:2-->
'''Hint''': If you need to send data for Entities or BlockEntities, it may be more appropriate to use the inbuilt network methods for these. Check out the [http://apidocs.vintagestory.at/api/Vintagestory.API.Server.IServerNetworkAPI.html IServerNetworkAPI] and [http://apidocs.vintagestory.at/api/Vintagestory.API.Client.IClientNetworkAPI.html IClientNetworkAPI] interfaces for more information.


== Introduction ==
== Introduction == <!--T:3-->


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.
<!--T:4-->
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 in a network channel.


<!--T:5-->
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.
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.


== Preparation
If something goes wrong, Wireshark with the [https://github.com/bluelightning32/vs-protocol vs-protocol] dissector is a helpful debug tool.


== Preparation == <!--T:6-->
<!--T:7-->
Let's start by creating a new .cs file for this mod, and adding our imports and the namespace in which we'll wrap all of our classes:
Let's start by creating a new .cs file for this mod, and adding our imports and the namespace in which we'll wrap all of our classes:
 
</translate>
<syntaxhighlight lang="c#">
<syntaxhighlight lang="c#">
using System;
using ProtoBuf;
using ProtoBuf;
using Vintagestory.API.Client;
using Vintagestory.API.Client;
using Vintagestory.API.Common;
using Vintagestory.API.Common;
using Vintagestory.API.Common.Entities;
using Vintagestory.API.Config;
using Vintagestory.API.Config;
using Vintagestory.API.Server;
using Vintagestory.API.Server;
Line 30: Line 38:
== ProtoContracts ==
== 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 [https://developers.google.com/protocol-buffers/docs/csharptutorial here].
VintageStory uses multiple [[Modding:Serialization_Formats|serialization formats]]. This tutorial uses the protobuf format. The [https://github.com/protobuf-net/protobuf-net Protobuf-net] library can serialize classes declared with the <code>ProtoContract</code> attribute.  


Inside our namespace block, let's create our classes:
Inside our namespace block, let's create 2 data packets using <code>ProtoContract</code>:


<syntaxhighlight lang="c#">
<syntaxhighlight lang="c#">
[ProtoContract(ImplicitFields = ImplicitFields.AllPublic)]
    [ProtoContract]
     public class NetworkApiTestMessage
     public class NetworkApiTestMessage
     {
     {
        [ProtoMember(1)]
         public string message;
         public string message;
     }
     }


     [ProtoContract(ImplicitFields = ImplicitFields.AllPublic)]
     [ProtoContract]
     public class NetworkApiTestResponse
     public class NetworkApiTestResponse
     {
     {
        [ProtoMember(1)]
         public string response;
         public string response;
     }
     }
</syntaxhighlight>
</syntaxhighlight>
The argument we pass to <code>ProtoMember</code> is a unique unsigned integer called a tag, and it's used to identify the fields flagged for serialization. When the client receives the packet containing the serialized <code>ProtoContract</code>, it won't know the type of its members, and will identify them by the tag number. We could also use the class header <code>[ProtoContract(ImplicitFields = ImplicitFields.AllPublic)]</code> and skip all [ProtoMember] tags to flag all public fields for serialization, then tags are assigned in alphabetic order. ProtoBuf is beyond the scope of this tutorial, but you can read more about this powerful library [https://developers.google.com/protocol-buffers/docs/csharptutorial here].


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


== NetWorkApiTest ==  
== NetworkApiTest ==  


Now we will create our main class which inherits from the <code>ModSystem</code> class, which is the base class for mod systems. You can read more about this [http://apidocs.vintagestory.at/api/Vintagestory.API.Common.ModSystem.html here]. Moreover, we'll be separating our Client and Server sides within the class with region blocks for readability purposes.
Now we will create our main class that inherits from the <code>ModSystem</code> class, which is the base class for mod systems. You can read more about this [http://apidocs.vintagestory.at/api/Vintagestory.API.Common.ModSystem.html here]. Moreover, we'll be separating our Client and Server sides within the class with region blocks for readability purposes.


<syntaxhighlight lang="c#">
<syntaxhighlight lang="c#">
public class NetworkApiTest : ModSystem
    public class NetworkApiTest : ModSystem
     {
     {
        public override void Start(ICoreAPI api)
        {
            api.Network
                .RegisterChannel("networkapitest")
                .RegisterMessageType(typeof(NetworkApiTestMessage))
                .RegisterMessageType(typeof(NetworkApiTestResponse))
            ;
        }
         #region Client
         #region Client
             //Client side here
             // Client side here
         #endregion
         #endregion


         #region Server
         #region Server
             //Server side here
             // Server side here
         #endregion  
         #endregion  
     }
     }
</syntaxhighlight>
</syntaxhighlight>


Next, we'll set up our network channel on the server side, and register a server command to dispatch our message.
The Core API contains the Network property, which provides us with the <code>RegisterChannel</code> method. We pass this method a string containing the name we choose for our channel, in this case we'll call our channel "networkapitest". The code in Start() is executed on the server as well as the client. We now have two connected endpoints.
 
The system still needs to know what kind of packages you will want to send. We do this with the <code>RegisterMessageType</code> method.
 


== Server Setup ==
== Server Setup ==


After declaring our Server Side fields, we create an override for the <code>StartServerSide</code> method. This method of the <code>ModSystem</code> is called for all mods on the Server Side by the game.
Next, we'll set up our network channel on the Server side, and register a server command to dispatch our message.


<syntaxhighlight lang="c#">
<syntaxhighlight lang="c#">
#region Server
#region Server
IServerNetworkChannel serverChannel;
        IServerNetworkChannel serverChannel;
         ICoreServerAPI serverApi;
         ICoreServerAPI serverApi;


Line 82: Line 105:
             serverApi = api;
             serverApi = api;


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


             api.RegisterCommand("nwtest", "Send a test network message", "", OnNwTestCmd, Privilege.controlserver);
             api.ChatCommands.Create("nwtest")
                .WithDescription("Send a test network message")
                .RequiresPrivilege(Privilege.controlserver)
                .HandleWith(new OnCommandDelegate(OnNwTestCmd));
         }
         }
#endregion
#endregion
</syntaxhighlight>
</syntaxhighlight>


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 call <code>SetMessageHandler<T></code> on our channel, which takes a delegate that will be called every time when the given type of Packet is received from a client, in this case <code>NetworkApiTestResponse</code> will be handled by <code>OnClientMessage</code> which we'll declare later.
 
We then register the ProtoContract classes that we'll be using to this channel with the <code>RegisterMessageType</code> method.
 
Finally, we call <code>SetMessageHandler<T></code>, which takes a delegate that will be called every time a ProtoContract of type <code>T</code> is received from a client, in this case <code>NetworkApiTestResponse</code> will be handled by <code>OnClientMessage</code> 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.
Now that our channel is fully set up on the Server side, we'll want to register a server command that will broadcast a network message to all the clients listening to our "networkapitest" channel.


<syntaxhighlight lang="c#">
<syntaxhighlight lang="c#">
#region Server
#region Server
IServerNetworkChannel serverChannel;
        IServerNetworkChannel serverChannel;
         ICoreServerAPI serverApi;
         ICoreServerAPI serverApi;


         public override void StartServerSide(ICoreServerAPI api)...
         public override void StartServerSide(ICoreServerAPI api)...


private void OnNwTestCmd(IServerPlayer player, int groupId, CmdArgs args)
        private TextCommandResult OnNwTestCmd(TextCommandCallingArgs args)
         {
         {
             serverChannel.BroadcastPacket(new NetworkApiTestMessage()
             serverChannel.BroadcastPacket(new NetworkApiTestMessage()
Line 115: Line 134:
                 message = "Hello World!",
                 message = "Hello World!",
             });
             });
            return TextCommandResult.Success();
         }
         }
#endregion
#endregion
</syntaxhighlight>
</syntaxhighlight>


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


== Client Setup ==
== Client Setup ==
Line 127: Line 147:
<syntaxhighlight lang="c#">
<syntaxhighlight lang="c#">
#region Client
#region Client
IClientNetworkChannel clientChannel;
        IClientNetworkChannel clientChannel;
         ICoreClientAPI clientApi;
         ICoreClientAPI clientApi;


Line 134: Line 154:
             clientApi = api;
             clientApi = api;


             clientChannel =
             clientChannel = api.Network.GetChannel("networkapitest")
                api.Network.RegisterChannel("networkapitest")
                .RegisterMessageType(typeof(NetworkApiTestMessage))
                .RegisterMessageType(typeof(NetworkApiTestResponse))
                 .SetMessageHandler<NetworkApiTestMessage>(OnServerMessage)
                 .SetMessageHandler<NetworkApiTestMessage>(OnServerMessage)
             ;
             ;
Line 144: Line 161:
</syntaxhighlight>
</syntaxhighlight>


On the Client side, we set up the "networkapitest" channel much in the same way as we did on the Server side. The <code>IClientNetworkChannel</code> type is the client side equivalent of the <code>IServerNetworkChannel</code>, and shares much of the same functionality.
Also on the client we call <code>SetMessageHandler</code> to listen for messages of type <code>NetworkApiTestMessage</code>, using a delegate named <code>OnServerMessage</code>.
 
This time we call <code>SetMessageHandler</code> to listen for messages of type <code>NetworkApiTestMessage</code>, using a delegate named <code>OnServerMessage</code>.


<syntaxhighlight lang="c#">
<syntaxhighlight lang="c#">
#region Client
#region Client
IClientNetworkChannel clientChannel;
        IClientNetworkChannel clientChannel;
         ICoreClientAPI clientApi;
         ICoreClientAPI clientApi;


         public override void StartClientSide(ICoreClientAPI api)...
         public override void StartClientSide(ICoreClientAPI api)...


private void OnServerMessage(NetworkApiTestMessage networkMessage)
        private void OnServerMessage(NetworkApiTestMessage networkMessage)
         {
         {
             clientApi.ShowChatMessage("Received following message from server: " + networkMessage.message);
             clientApi.ShowChatMessage("Received following message from server: " + networkMessage.message);
Line 175: Line 190:
== Handling Client Responses ==
== Handling Client Responses ==


In our Server side region, let's write a final method:
In our Server side region, let's write a final delegate method:


<syntaxhighlight lang="c#">
<syntaxhighlight lang="c#">
#region Server
#region Server
IServerNetworkChannel serverChannel;
        IServerNetworkChannel serverChannel;
         ICoreServerAPI serverApi;
         ICoreServerAPI serverApi;


         public override void StartServerSide(ICoreServerAPI api)...
         public override void StartServerSide(ICoreServerAPI api)...


private void OnNwTestCmd(IServerPlayer player, int groupId, CmdArgs args)...
        private void OnNwTestCmd(IServerPlayer player, int groupId, CmdArgs args)...


private void OnClientMessage(IPlayer fromPlayer, NetworkApiTestResponse networkMessage)
        private void OnClientMessage(IPlayer fromPlayer, NetworkApiTestResponse networkMessage)
         {
         {
             serverApi.SendMessageToGroup(
             serverApi.SendMessageToGroup(
Line 197: Line 212:
</syntaxhighlight>
</syntaxhighlight>


Messages handlers on the server have an additional parameter compared to client handlers, which is the player whose client sent the message.
Message 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.
In this handler we simply broadcast a server-wide message containing the responding player's name and the content of the response.
Line 206: Line 221:


<syntaxhighlight lang="c#">
<syntaxhighlight lang="c#">
using System;
using ProtoBuf;
using ProtoBuf;
using Vintagestory.API.Client;
using Vintagestory.API.Client;
using Vintagestory.API.Common;
using Vintagestory.API.Common;
using Vintagestory.API.Common.Entities;
using Vintagestory.API.Config;
using Vintagestory.API.Config;
using Vintagestory.API.Server;
using Vintagestory.API.Server;
Line 216: Line 229:
namespace Vintagestory.ServerMods
namespace Vintagestory.ServerMods
{
{
     [ProtoContract(ImplicitFields = ImplicitFields.AllPublic)]
     [ProtoContract]
     public class NetworkApiTestMessage
     public class NetworkApiTestMessage
     {
     {
        [ProtoMember(1)]
         public string message;
         public string message;
     }
     }


     [ProtoContract(ImplicitFields = ImplicitFields.AllPublic)]
     [ProtoContract]
     public class NetworkApiTestResponse
     public class NetworkApiTestResponse
     {
     {
        [ProtoMember(1)]
         public string response;
         public string response;
     }
     }
Line 233: Line 248:
     public class NetworkApiTest : ModSystem
     public class NetworkApiTest : ModSystem
     {
     {
        #region Client
         public override void Start(ICoreAPI api)
        IClientNetworkChannel clientChannel;
        ICoreClientAPI clientApi;
 
         public override void StartClientSide(ICoreClientAPI api)
         {
         {
             clientApi = api;
             api.Network
 
                 .RegisterChannel("networkapitest")
            clientChannel =
                 api.Network.RegisterChannel("networkapitest")
                 .RegisterMessageType(typeof(NetworkApiTestMessage))
                 .RegisterMessageType(typeof(NetworkApiTestMessage))
                 .RegisterMessageType(typeof(NetworkApiTestResponse))
                 .RegisterMessageType(typeof(NetworkApiTestResponse))
                .SetMessageHandler<NetworkApiTestMessage>(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!"
            });
        }
        #endregion


         #region Server
         #region Server
Line 269: Line 265:
             serverApi = api;
             serverApi = api;


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


             api.RegisterCommand("nwtest", "Send a test network message", "", OnNwTestCmd, Privilege.controlserver);
             api.ChatCommands.Create("nwtest")
                .WithDescription("Send a test network message")
                .RequiresPrivilege(Privilege.controlserver)
                .HandleWith(new OnCommandDelegate(OnNwTestCmd));
         }
         }


         private void OnNwTestCmd(IServerPlayer player, int groupId, CmdArgs args)
         private TextCommandResult OnNwTestCmd(TextCommandCallingArgs args)
         {
         {
             serverChannel.BroadcastPacket(new NetworkApiTestMessage()
             serverChannel.BroadcastPacket(new NetworkApiTestMessage()
Line 285: Line 281:
                 message = "Hello World!",
                 message = "Hello World!",
             });
             });
            return TextCommandResult.Success();
         }
         }


Line 296: Line 293:
         }
         }


          
         #endregion
        #region Client
        IClientNetworkChannel clientChannel;
        ICoreClientAPI clientApi;
 
        public override void StartClientSide(ICoreClientAPI api)
        {
            clientApi = api;
 
            clientChannel = api.Network.GetChannel("networkapitest")
                .SetMessageHandler<NetworkApiTestMessage>(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!"
            });
        }
 
         #endregion
         #endregion
     }
     }
Line 306: Line 326:
Let's run the mod now! Once you're ingame, try entering <code>/nwtest</code>. You should be met with an initial "Hello World!" message as well as a confirmation message that the server received the response from the Client!
Let's run the mod now! Once you're ingame, try entering <code>/nwtest</code>. You should be met with an initial "Hello World!" message as well as a confirmation message that the server received the response from the Client!


== Distribution ==
{{Navbox/codemodding}}
 
To distribute this mod, you may run the following command in the modtools cli <code>pack <your mod id></code>, then copy the .zip file into your VintageStory mods folder.
 
Here are the official versions:
* for VS v1.10.: [https://wiki.vintagestory.at/images/7/73/NetworkApiTest_vs1.10-rc.4_v1.0.0.zip NetworkApiTest_vs1.10_v1.0.0.zip]
 
{{Navbox/modding|Vintage Story}}

Latest revision as of 14:10, 13 July 2024

This page was last verified for Vintage Story version 1.19.8.

Other languages:

Before starting, you should have a development environment set up. If you don't have one already you should read the tutorial Setting up your Development Environment. Furthermore, we assume that you have a basic understanding of the C# language and Object Oriented Programming. Let's get started!

Hint: If you need to send data for Entities or BlockEntities, it may be more appropriate to use the inbuilt network methods for these. Check out the IServerNetworkAPI and IClientNetworkAPI interfaces for more information.

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 in 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.

If something goes wrong, Wireshark with the vs-protocol dissector is a helpful debug tool.

Preparation

Let's start by creating a new .cs file for this mod, and adding our imports and the namespace in which we'll wrap all of our classes:

using ProtoBuf;
using Vintagestory.API.Client;
using Vintagestory.API.Common;
using Vintagestory.API.Config;
using Vintagestory.API.Server;

namespace Vintagestory.ServerMods
{

}

ProtoContracts

VintageStory uses multiple serialization formats. This tutorial uses the protobuf format. The Protobuf-net library can serialize classes declared with the ProtoContract attribute.

Inside our namespace block, let's create 2 data packets using ProtoContract:

    [ProtoContract]
    public class NetworkApiTestMessage
    {
        [ProtoMember(1)]
        public string message;
    }

    [ProtoContract]
    public class NetworkApiTestResponse
    {
        [ProtoMember(1)]
        public string response;
    }

The argument we pass to ProtoMember is a unique unsigned integer called a tag, and it's used to identify the fields flagged for serialization. When the client receives the packet containing the serialized ProtoContract, it won't know the type of its members, and will identify them by the tag number. We could also use the class header [ProtoContract(ImplicitFields = ImplicitFields.AllPublic)] and skip all [ProtoMember] tags to flag all public fields for serialization, then tags are assigned in alphabetic order. ProtoBuf is beyond the scope of this tutorial, but you can read more about this powerful library here.

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.

NetworkApiTest

Now we will create our main class that inherits from the ModSystem class, which is the base class for mod systems. You can read more about this here. Moreover, we'll be separating our Client and Server sides within the class with region blocks for readability purposes.

    public class NetworkApiTest : ModSystem
    {
        public override void Start(ICoreAPI api)
        {
            api.Network
                .RegisterChannel("networkapitest")
                .RegisterMessageType(typeof(NetworkApiTestMessage))
                .RegisterMessageType(typeof(NetworkApiTestResponse))
            ;
        }
        #region Client
            // Client side here
        #endregion

        #region Server
            // Server side here
        #endregion 
    }

The Core API contains the Network property, 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". The code in Start() is executed on the server as well as the client. We now have two connected endpoints.

The system still needs to know what kind of packages you will want to send. We do this with the RegisterMessageType method.


Server Setup

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

#region Server
        IServerNetworkChannel serverChannel;
        ICoreServerAPI serverApi;

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

            serverChannel = api.Network.GetChannel("networkapitest")
                .SetMessageHandler<NetworkApiTestResponse>(OnClientMessage)
            ;

            api.ChatCommands.Create("nwtest")
                .WithDescription("Send a test network message")
                .RequiresPrivilege(Privilege.controlserver)
                .HandleWith(new OnCommandDelegate(OnNwTestCmd));
        }
#endregion

We call SetMessageHandler<T> on our channel, which takes a delegate that will be called every time when the given type of Packet is received from a client, in this case NetworkApiTestResponse will be handled by OnClientMessage which we'll declare later.

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

#region Server
        IServerNetworkChannel serverChannel;
        ICoreServerAPI serverApi;

        public override void StartServerSide(ICoreServerAPI api)...

        private TextCommandResult OnNwTestCmd(TextCommandCallingArgs args)
        {
            serverChannel.BroadcastPacket(new NetworkApiTestMessage()
            {
                message = "Hello World!",
            });
            return TextCommandResult.Success();
        }
#endregion

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

Above the Server Side region, we'll create our Client fields and an override for the StartClientSide, which is a Client side version of its Server side counterpart.

#region Client
        IClientNetworkChannel clientChannel;
        ICoreClientAPI clientApi;

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

            clientChannel = api.Network.GetChannel("networkapitest")
                .SetMessageHandler<NetworkApiTestMessage>(OnServerMessage)
            ;
        }
#endregion

Also on the client we call SetMessageHandler to listen for messages of type NetworkApiTestMessage, using a delegate named OnServerMessage.

#region Client
        IClientNetworkChannel clientChannel;
        ICoreClientAPI clientApi;

        public override void StartClientSide(ICoreClientAPI api)...

        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!"
            });
        }
#endregion

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 Responses

In our Server side region, let's write a final delegate method:

#region Server
        IServerNetworkChannel serverChannel;
        ICoreServerAPI serverApi;

        public override void StartServerSide(ICoreServerAPI api)...

        private void OnNwTestCmd(IServerPlayer player, int groupId, CmdArgs args)...

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

Message 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.

Conclusion

If you followed the steps correctly, you should have the following code:

using ProtoBuf;
using Vintagestory.API.Client;
using Vintagestory.API.Common;
using Vintagestory.API.Config;
using Vintagestory.API.Server;

namespace Vintagestory.ServerMods
{
    [ProtoContract]
    public class NetworkApiTestMessage
    {
        [ProtoMember(1)]
        public string message;
    }

    [ProtoContract]
    public class NetworkApiTestResponse
    {
        [ProtoMember(1)]
        public string response;
    }

    /// <summary>
    /// A basic example of client<->server networking using a custom network communication
    /// </summary>
    public class NetworkApiTest : ModSystem
    {
        public override void Start(ICoreAPI api)
        {
            api.Network
                .RegisterChannel("networkapitest")
                .RegisterMessageType(typeof(NetworkApiTestMessage))
                .RegisterMessageType(typeof(NetworkApiTestResponse))
            ;
        }

        #region Server
        IServerNetworkChannel serverChannel;
        ICoreServerAPI serverApi;

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

            serverChannel = api.Network.GetChannel("networkapitest")
                .SetMessageHandler<NetworkApiTestResponse>(OnClientMessage)
            ;

            api.ChatCommands.Create("nwtest")
                .WithDescription("Send a test network message")
                .RequiresPrivilege(Privilege.controlserver)
                .HandleWith(new OnCommandDelegate(OnNwTestCmd));
        }

        private TextCommandResult OnNwTestCmd(TextCommandCallingArgs args)
        {
            serverChannel.BroadcastPacket(new NetworkApiTestMessage()
            {
                message = "Hello World!",
            });
            return TextCommandResult.Success();
        }

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

        #endregion
        #region Client
        IClientNetworkChannel clientChannel;
        ICoreClientAPI clientApi;

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

            clientChannel = api.Network.GetChannel("networkapitest")
                .SetMessageHandler<NetworkApiTestMessage>(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!"
            });
        }

        #endregion
    }
}

Testing

Let's run the mod now! Once you're ingame, try entering /nwtest. You should be met with an initial "Hello World!" message as well as a confirmation message that the server received the response from the Client!


Code Modding
Basics Code Mods Preparing For Code Mods Creating A Code Mod
Tutorials
Advanced Server-Client Considerations Setting up your Development Environment Advanced Blocks Advanced Items Block and Item Interactions Block Behavior Block Entity Particle Effects World Access Inventory Handling Commands GUIs Network API Monkey patching (Harmony)
Data Management VCDBS format Savegame Moddata ModConfig File Chunk Moddata Serialization Formats TreeAttribute
Worldgen WorldGen API NatFloat EvolvingNatFloat
Rendering Shaders and Renderers
Icon Sign.png

Wondering where some links have gone?
The modding navbox is going through some changes! Check out Navigation Box Updates for more info and help finding specific pages.

Modding
Modding Introduction Getting Started Theme Pack
Content Modding Content Mods Developing a Content Mod Basic Tutorials Intermediate Tutorials Advanced Tutorials Content Mod Concepts
Code Modding Code Mods Setting up your Development Environment
Property Overview ItemEntityEntity BehaviorsBlockBlock BehaviorsBlock ClassesBlock EntitiesBlock Entity BehaviorsCollectible BehaviorsWorld properties
Workflows & Infrastructure Modding Efficiency TipsMod-engine compatibilityMod ExtensibilityVS Engine
Additional Resources Community Resources Modding API Updates Programming Languages List of server commandsList of client commandsClient startup parametersServer startup parameters
Example ModsAPI DocsGitHub Repository