Confirmedusers
536
edits
No edit summary |
m (Updated navbox to new code navbox.) |
||
(28 intermediate revisions by 9 users not shown) | |||
Line 1: | Line 1: | ||
__FORCETOC__ | __FORCETOC__ | ||
{{GameVersion|1.19.3}} | |||
<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! | |||
<!--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 | <!--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 System; | ||
Line 30: | Line 40: | ||
== ProtoContracts == | == ProtoContracts == | ||
VintageStory uses the | 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 our <code>ProtoContract</code> classes: | ||
<syntaxhighlight lang="c#"> | <syntaxhighlight lang="c#"> | ||
[ProtoContract(ImplicitFields = ImplicitFields.AllPublic)] | [ProtoContract(ImplicitFields = ImplicitFields.AllPublic)] | ||
public class NetworkApiTestMessage | public class NetworkApiTestMessage | ||
{ | { | ||
Line 47: | Line 57: | ||
} | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
The argument we pass to <code>ProtoContract</code> makes it so all <code>public</code> fields will be serialized; without this, we'd declare fields for serialization by using <code>ProtoMember</code> attributes. If we chose this approach, we'd write our classes like so: | |||
<syntaxhighlight lang="c#"> | |||
[ProtoContract] | |||
public class NetworkApiTestMessage | |||
{ | |||
[ProtoMember(1)] | |||
public string message; | |||
} | |||
[ProtoContract] | |||
public class NetworkApiTestResponse | |||
{ | |||
[ProtoMember(1)] | |||
public string response; | |||
} | |||
</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 use the <code>ImplicitFields.AllPublic</code> to flag all public fields for serialization, and 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 == | ||
Now we will create our main class | 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 | ||
{ | { | ||
#region Client | #region Client | ||
Line 67: | Line 96: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
Next, we'll set up our network channel on the | Next, we'll set up our network channel on the Server side, and register a server command to dispatch our message. | ||
== Server Setup == | == Server Setup == | ||
After declaring our Server | 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. | ||
<syntaxhighlight lang="c#"> | <syntaxhighlight lang="c#"> | ||
#region Server | #region Server | ||
IServerNetworkChannel serverChannel; | IServerNetworkChannel serverChannel; | ||
ICoreServerAPI serverApi; | ICoreServerAPI serverApi; | ||
Line 89: | Line 118: | ||
; | ; | ||
api. | 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. | The Core Server API contains the Network member, 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". 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 <code>RegisterMessageType</code> method. | 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. | Finally, we call <code>SetMessageHandler<T></code>, which takes a delegate that will be called every time a <code>ProtoContract</code> 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 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 | private TextCommandResult OnNwTestCmd(TextCommandCallingArgs args) | ||
{ | { | ||
serverChannel.BroadcastPacket(new NetworkApiTestMessage() | serverChannel.BroadcastPacket(new NetworkApiTestMessage() | ||
Line 115: | Line 147: | ||
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 160: | ||
<syntaxhighlight lang="c#"> | <syntaxhighlight lang="c#"> | ||
#region Client | #region Client | ||
IClientNetworkChannel clientChannel; | IClientNetworkChannel clientChannel; | ||
ICoreClientAPI clientApi; | ICoreClientAPI clientApi; | ||
Line 150: | Line 183: | ||
<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 208: | ||
== 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 230: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
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 233: | Line 266: | ||
public class NetworkApiTest : ModSystem | public class NetworkApiTest : ModSystem | ||
{ | { | ||
#region Server | #region Server | ||
IServerNetworkChannel serverChannel; | IServerNetworkChannel serverChannel; | ||
Line 276: | Line 281: | ||
; | ; | ||
api. | api.ChatCommands.Create("nwtest") | ||
.WithDescription("Send a test network message") | |||
.RequiresPrivilege(Privilege.controlserver) | |||
.HandleWith(new OnCommandDelegate(OnNwTestCmd)); | |||
} | } | ||
private | private TextCommandResult OnNwTestCmd(TextCommandCallingArgs args) | ||
{ | { | ||
serverChannel.BroadcastPacket(new NetworkApiTestMessage() | serverChannel.BroadcastPacket(new NetworkApiTestMessage() | ||
Line 285: | Line 293: | ||
message = "Hello World!", | message = "Hello World!", | ||
}); | }); | ||
return TextCommandResult.Success(); | |||
} | } | ||
Line 296: | Line 305: | ||
} | } | ||
#endregion | |||
#region Client | |||
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) | |||
; | |||
} | |||
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 341: | ||
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! | ||
{{Navbox/codemodding}} | |||
{{Navbox/ |