I remember when I started MP coding I had this burning question, how do I call a function on the server from a client? I asked around but could not get a straight, clear answer either. So today I’m going to pretend I’m the same script newbie all over again and revisit this issue.

So let’s start from the beginning. In single player/editor mode everything is happening on your PC and your PC only. This means all functions you make can be called from anywhere in your scripts provided you did not make them private, i.e. used private variable to define function code.

_myFunction = { //available only in this script }; myFunction = { //available in all scripts };

So while _myFunction is only available within the script scope you defined it in, myFunction will be available to any script running on your PC, including availability to the classes, which are initialised after myFunction definition. But when you are in MP environment, each connected PC is an entirely different, separate script domain. So myFunction defined on my PC will not exist on other connected PCs unless I do something about it.

One of the ways to define myFunction on every PC is to broadcast it. This could be simply done with publicVariable command. publicVariable will broadcast your variable to every connected PC as well as to every PC that joins your network after the broadcast. Joining In Progress clients are called JIP. JIP clients will reliably receive your broadcast variable as they join.

myFunction = { //available on this PC only }; publicVariable "myFunction"; //now myFunction is available on all PCs

Oh yeah, let me just rewind a bit and tell you what happens when you start your server and join it. You would usually have 3 files in your mission: mission.sqm, description.ext and init.sqf. These files would normally be .pbo-ed and reside in MPMissions folder on the dedicated server (I will continue with dedicated server setup in mind from now on).

When player joins the server for the first time he will download the mission files from the server and run them on his PC automatically. But before that, the server will also run those mission files just before they get executed on the first joining client. The server essentially connects to itself with the name __SERVER__ and runs mission files on the server PC prior to anyone. The mission files will then subsequently be executed on second, third, fourth etc., connecting PC.

So if you want to have your myFunction on every machine, you can just simply add it in init.sqf for example. So either adding initially or broadcasting, we have the ability to make a variable global. If you are planning on changing the value of your global variable however, you will need to broadcast the changes with publicVariable command every time, to keep all PCs in sync.

Now, I’m ashamed to admit, but I had real difficulty understanding isServer and isDedicated commands and their purpose. Both command indicate if the environment they are executed in is server or not. If you host a mission then your PC will be the server and so isServer will be true on it, but it will not be a separate dedicated server so isDedicated will be false on it. Since you can as easily start a dedicated server on your PC and connect to it, I suggest you focus on dedicated server environment straight away. Here is the sample use of isDedicated inside init.sqf:

if (isDedicated) then { //this code will run only once on the server PC } else { //this code will run on every joining client };

This command is used in scripts that you know will run on both client and server, like init.sqf, mission.sqm and description.ext. If you have a script that is called from init.sqf for example, this script will also run on both server and client, and a script called from that other script will also run on both server and client, and so on. This is why you need isDedicated so you can manage what runs where and when.

So now to the main issue. How on Earth can you execute a function that has only been defined on the server, from a client? The only way you can directly talk to the server is via variable broadcast, as we established above. When you send publicVariable from a client, everyone including the server will receive it. But there is also a way to make a client or the server to listen to the variable broadcast and execute predefined code once broadcast variable update is received.

You can do so by executing addPublicVariableEventHandler command on the machine that is supposed to listen to the broadcast, in our case – the server. The command will pass the value of your broadcast variable to the code you defined and execute it on the PC where the event handler was added. Essentially by assigning event handler to the variable on one machine and broadcasting it on the other you achieve remote execution, as simple as that, and this is how you talk to the server.

"broadcastVar" addPublicVariableEventHandler { _broadcastVarName = _this select 0; _broadcastVarValue = _this select 1; //do whatever you want with the data here... };

But what if you need to receive a reply from the server? You can use the same principle and add event handler on your client so that the server can talk back to your client in the same way. In this case you might want to use publicVariable commands that are more suitable for this task – publicVariableServer and publicVariableClient. Here is the sample code to use in init.sqf. When you send 2 numbers to the server, the server is supposed to add them together and send the sum right back:

if (isDedicated) then { "packet" addPublicVariableEventHandler { _pcid = owner (_this select 1 select 0); _number1 = _this select 1 select 1; _number2 = _this select 1 select 2; _thesum = _number1 + _number2; packet = _thesum; _pcid publicVariableClient "packet"; }; } else { "packet" addPublicVariableEventHandler { _thesum = _this select 1; hint str _thesum; }; };

Let me explain step by step what is happening here. We use isDedicated command to make sure the client and the server execute only their own code, since init.sqf will initially run on both client and the server. We will use a variable named let’s say “packet” as a vessel with which we move data back and forth. On the server we will expect “packet” to be an array [player, number1, number2]. Why do we need player param? Because publicVariableClient needs to know to which PC to send it, and we can obtain the id of the owner of that PC from the player object.

After extracting all the data from variable value we do the addition and broadcast the sum in the same variable “packet”. We can do this simply because the public variable event handler does not trigger on the broadcasting machine. On the client we expect the broadcast variable “packet” be a number, so the “packet” is now a number. When it arrives we simply str hint it. After we have set up our event handlers waiting for the broadcast, we simply test it by broadcasting our “packet” from a client:

packet = [player, 22, 44]; publicVariableServer "packet";

If everything is done correctly, you should see a hint with number 66 in it. In this case we used publicVariableServer because we only want the server to receive the broadcast. If we use publicVariable instead, all other clients will receive the broadcast as well, and since client side event handler is waiting for a number, receiving array instead will not be the result we expect. So this is it, basic multiplayer coding in a nutshell.


Further reading: Locality