Table of Contents

Multiplayer Tutorial

The purpose of this tutorial is to discuss some of the scripting issues that are particular to the creation of multi-player missions. It is assumed that the reader is at least a competent scripter in a single-player setting.

Locality

Locality of Objects

The locality of an object determines which computer is the owner of this object. Many commands affect only to local objects, so the locality must be considered when we want to execute these commands over existing objects.

As can be seen, the locality of a unit might change during a game. For example, player A is driver of car B, so car B is local to the computer of player A. When player A gets out then car B will become local to the server. When player C becomes the new driver, the car will become local to player C's computer. The same goes for units which change groups or whose group leader changes.

Locality of markers

Any marker placed in the editor is global and will be seen by any player. If you want markers to be local for a particular machine (only visible from this machine) create it using createMarkerLocal (and use the corresponding local commands to configure it).

Locality of commands

The locality of a command covers two different concepts:

There are commands which require that the unit affected to be local to the machine where the command is executed. Other commands will affect any unit, local or not, from where they are executed. For example, if you set the velocity vector of a vehicle from a machine where this vehicle is not local, the command will have not effect at all, and not because the effects of the command are not global, but because the command requires that the affected unit must be local to the machine where the command is executed.

The effects of each command might also be local or global. If the effect of the command is not global, the effects will be “seen” only in the machine where the command has been executed. For example, titleText, since the text displayed will be seen only where the command is being executed. The same is true of say or playSound, or any of the chat commands. To ensure a global effect using local effect commands you need to transmit some info that triggers the same command in every machine; for that purpose you may use triggers and the publicVariable command. (Ah, before you ask, the drop, and other particle effect, commands may be executed on any machine, but the effects of it are local, so you should take care to execute particle scripts on every machine to have similar effects on all of them).

Check the BIS WIKI for more detailed information about which commands only affect local units and which commands have only a local effect.

Determining the locality of an object

Using the local comand:

if (local unit) then

Example:

if (local player) then
{
	hint "There is a player here, I might be a non-dedicated server or just a client";
};
 
if (!local player) then
{
	hint "No here player, I'm a dedicated server";
};
 
if (!local vehiclename) then
{
	hint "This vehicle is not local to this machine, do don't use setVelocity, setVectorDir or setVectorUp commands here";
};
 
if (local vehiclename) then
{
	hint "This vehicle is local to this machine, so we may use setVelocity, setVectorDir or setVectorUp commands here";
};
 
if (local server) then
{
	hint "I'm the server, dedicated or not";
};
//(note that a gamelogic named "server" is not needed anymore, since the introduction of the isServer command)
 
if (!local server) then
{
	hint "I'm a client";
};

Determining whether the script is running on a client or the server

In older versions of the game, the most common way to check for server was to add a gamelogic named “server” from the editor to the mission, and then just do the following check:

if (local server) then
{

This workaround is actually not needed any more, although it still works correctly and you will still see it used in older scripts, since the introduction of the isServer command (in ArmA 1.07):

if (isServer && !local player) then
{
	hint "I'm a dedicated server";
};
 
if (isServer && local player) then
{
	hint "I'm a single-player host (client & server)";
};

The importance of determining where the server is

For example, your mission might have many scripts that should not be executed on every connected machine and that isn't required to affect non-local units. For these cases you have two options:

There where you want to execute the script (for example, init.sqs or init.sqf), place a condition first:

if (isServer) then
{
	[] execVM "myscript.sqf";
};

Alternatively, put a guard condition inside the script itself, which will stop the script at that point if it is the server. Code before this guard condition will run on all machines, code after it will run only on the server:

// <--- Code run on every machine up to this point
if (!isServer) exitWith {};
// <--- Code run only on the server after this point

Where script files, and scripts input into the editor, are executed

In many situations we must execute a script or command only once, not in every machine connected, or we must execute some code only there were a particular unit is local, or only in the server. To start with, let's see where the code is executed by default:

Running object initialization code on every machine

For that purpose, use the setVehicleInit command to set the init commands of your newly created units. This is similar to writing these commands in the init field of a unit placed in the editor, but setVehicleInit doesn't have any immediate effect. You may set the init field of one or many existing units by executing setVehicleInit, but then you must execute the processInitCommands command to actually execute all the commands configured with setVehicleInit.

For example:

_plane = createVehicle [_objectClass, _position, [], 0, "NONE"];
_plane setVehicleInit "eh = this addEventHandler [""IncomingMissile"", {_this execVM ""mando_missiles\mando_replacemissile.sqf""}];";
processInitCommands;

processInitCommands ensures that the configured init commands for the units are executed on every client connected, even these clients that connect after the command is executed (JIP).

This technique for sending information has the advantage that it is immediately recieved by all machines without needing to have a script polling a variable to see if data is being sent. Although this method is most often used on newly created objects, it can be used to run code on every machine at any stage in the lifetime of an object.

Server and Clients

The simplest way to communicate is to use publicVariable, which will allow synchronisation of a global variable's value across all machines. Unfortunately, this simple technique will not allow the transfer of some data types (sides, arrays) and is limited in its usefulness, since it does not inform the other machines that anything has been changed.

Remote command execution

There are two ways to execute commands, whenever required, on other machines:

Both techniques have advantages and disadvantages, but, since the former is more generally appropriate, it will be assumed that the publicVariable technique, as described below, is being used in all future examples.

On every machine, there is a script continually looking to see if a global command has been requested:

global_command = "";
 
while {true} do
{
	waitUntil
	{
		global_command != ""
	};
 
	call compile global_command;
	global_command = "";
};

On the machine wishing to murder every player:

global_command = "player setDamage 1";
publicVariable "global_command";

If multiple commands are being sent at the same time, then either seperate them by semi-colons, or call a function.

Another way to execute remote commands is via dynamically created triggers, which action is executed everywhere :

_trigger = createTrigger ["EmptyDetector", [0, 0, 0]];
_trigger setTriggerActivation ["NONE", "PRESENT", false];
_trigger setTriggerArea [0, 0, 0, false];
_trigger setTriggerType "NONE";
_trigger setTriggerTimeout [0, 0, 0, false ];
_trigger setTriggerStatements ["true", "YOUR COMMANDS HERE", ""];
sleep 6;
deleteVehicle _trigger;

Debugging MP missions

Logging via scripts

One of the standard ways to debug scripts is to use hint or sideChat to output debugging messages. This is still a valid way to debug scripts running on the clients, but it will not work on the server, since anything requested to be displayed on the server is just ignored by the game. The only way to see messages generated by the server is to transmit it to the clients so that they can see it.

_message = "Detected someone in zone 2";
global_command = format ["hint ""%1"";", _message];
publicVariable "global_command";

Just as in single-player mode, this method has severe limitations, since the hint/chat areas are only of limited size and if many debug messages are shown in a short space of time, then messages might be missed. This will be even more of a problem in MP since you are monitoring both the client and server debug messages from one client. To overcome this, you could build your own server debug log dialog or use one of the user-made debug logs such as the one included in the SPON Core pack.

Viewing the ArmA logs

The game maintains its own logs, which contain records of errors on clients and servers. The log made by the server can be found at “C:\Documents and Settings\\Local Settings\Application Data\ArmA\arma_server.RPT”. The client version is called “arma.RPT” and is to be found in the same directory.

Testing MP missions on a single computer

Make a shortcut to start the ArmA server using a config file (the path might be different for you). The shortcut's Target should be:

arma_server.exe -config=server.cfg

The server.cfg file:

// Anyone connecting can become admin with a simple "#login"
passwordAdmin = "";

// Prevent server from broadcasting its existance to GameSpy, so there won't be any
// random people trying to connect, even if the game is on the Internet
reportingIP = "<>";

// Allow multiple clients to connect using just one copy of ArmA
kickduplicate = 0;

When running multiple clients on the same machine, it is often best to open them in a window (“arma.exe -window”) so that you can switch between them very easily. Although alt-tab can be used for this, it is slower and can cause the game to crash if it is used too much.

ArmA console commands

There is only one console command that is particularly useful for testing MP games, which is #monitor. This can be used by the server administrator (someone who has logged in using #login) to monitor the server from a client machine. This shows several useful items of information about the server machine:

The bandwidth values can give a good indication of whether scripts within a mission are using global commands too frequently, though, like the FPS value, they are severely affected by the number of players and mission complexity.

Glossary

There are a number of specific terms used in MP scripting for ArmA which are either ambiguous, inconsistent or used differently that in regular (non-ArmA) programming. For example, the use of “global” in terms of an object is quite different to the use of “global” in terms of a variable.