Table of Contents
ArmA 1 Multiplayer Tutorial
ArmA 1 Forum, ArmA 1 Home, ArmA 1 Config, ArmA 1 Tools, ArmA 1 File Formats, ArmA 1 Missions, ArmA 1 3D Modeling, ArmA 1 Terrain, ArmA 1 Texturing, ArmA 1 Scripting
ArmA 1 aka Armed Assault (ArmA)
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 script writer 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.
- The players: each player object is local to the corresponding client computer, or the server if server is not a dedicated one. Note that a dedicated server has no player, so the value of player on that machine is objNull.
- Group leaders that are not players (i.e. AI): local to the server.
- Any other AI soldier in a group: local to the computer where its leader is local.
- Empty vehicles: local to the server.
- “Empty” objects, such as ammo-boxes, which have been placed in the editor: local to the server.
- Occupied vehicles: local to the machine where the vehicle's driver is local.
- Gamelogics created within the editor or with createVehicle: always local to the server.
- Units created with createVehicleLocal: local to the client or server that executed the command (note that gamelogics might be created that way also). In many cases you might want to use gamelogics inside your scripts just for distance or angle calculations, and some of these scripts might be executed only by players (for example through an action menu). For these cases you don't need to create logics that are local to the server, but just local to the machine where the script is executed, so you may use createVehicleLocal command to create these logics and delete them, when they are not needed anymore, with the deleteVehicle command.
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:
- The locality of the unit that is affected by the command
- The locality of the effects that result from the command
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:
- Menu actions are executed only in the machine where the action is activated. That is, where the unit that activated the action is local.
- Triggers other than radio ones: the activation and deactivation code is executed on every machine connected where the condition is met.
- Waypoint activation commands are executed when the leader of the moving group is local.
- Eventhanders: in some cases they are executed globally, in others, locally. But always make sure the corresponding addEventHandler is executed on every machine, included any dedicated server.
- Code in init field of editor-placed units: executed on every machine, at game start, or when it joins the game (JIP).
- Code in init.sqf or init.sqs files: executed in every machine connected.
- Scripts executed from another script: executed there were the parent script is being executed.
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:
- Monitor a publicVariable for command requests. The key weakness of this method is that it could be possible that if two commands were received at exactly the same time, that is, more than one per frame, then only one of them would be detected and executed.
- Create a game-logic specifically to send commands across the network (technique similar to that described above). This has the advantages that all commands are guaranteed to be executed and that there is no script continually polling a value to see if a command has been requested, but has the disadvantage that JIP players will always have the last command requested run when they first connect. If necessary, this limitation could be overcome by resetting the init to “” after setting and running the init code.
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 FPS (frames per second) on the server. Although the server doesn't render any graphics, it still runs scripts and calculates all of the object positions and relations, just like the clients do, once every frame. The lower this number, the more strain is being placed on the server by a number of factors, including mission complexity, number of players and the number and complexity of running scripts.
- The memory used by the server.
- The outward bandwidth used (kbps). This is the amount of data traveling from the server to the clients.
- The inward bandwidth used (kbps). This is the total amount of data traveling from all the clients to the server.
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.
- Client: A machine on which a player is playing, whether it is a single- or multi-player game.
- Dedicated Server: The machine that controls a multi-player game.
- Global object: An object that is owned by one machine, but which can be seen by all machines.
- Global variable: A global variable can be seen by all scripts running on the local machine. Any variable that isn't pre-fixed with an underscore, such as frog, is automatically a global variable. Only global variables may be transmitted via the publicVariable command.
- Join in progress (JIP): In some multi-player games, players can join the game after it has started. When they join, any onPlayerConnected handler will be executed, only on the server, so if the other clients need to be aware of this, you will need to manually communicate this fact.
- Local object (Meaning 1: Local ownership, local visibility): An object that is owned by the machine running the script and which cannot be seen (interacted with) by any any other machine (local _object == true).
- Local object (Meaning 2: Local ownership, global visibility): A global object that is local to the client/server running the script (local _object == true).
- Local variable: A local variable is only visible within a single script execution, which, by its very nature is local to a single machine. All variables pre-fixed with an underscore, such as _frog, are automatically local variables. Only local variables can be declared as private
- Multi-player game (MP): A network game where each player is on a client machine and there is a dedicated server overseeing everything. If you host a multi-player game from within ArmA (arma.exe), then it will create a seperate server for you, so it will act just as if it were a seperate machine. Even if there is only one person playing a game of this type, it is still considered to be a multi-player game.
- Object: Anything that can be placed in the editor or created with createVehicle, createVehicleLocal, createUnit, camCreate or createTrigger.
- “Public” variable: One might call a global variable that has been copied to all machines using publicVariable a “public” variable, but this is misleading and should be avoided. Remember that publicVariable only updates the value of the variable at the moment the publicVariable command is executed; It does not change the nature of the variable in any way, but just broadcasts it and will not cause future changes to be broadcast.
- Private variable: A local variable can be made private through the use of the private command. This limits the visibility of the variable to the current scope, temporarily masking the value of any existing variable of the same name.
- Remote object (Remote ownership, global visibility): A global object which is local to a different machine (local _object == false).
- Server: The machine controlling the game and the player interactions.
- Single-player game (SP): A game run from within ArmA, such as the campaign, where the client and (non-dedicated) server are actually the same machine. This makes scripting considerably simpler, since one does not have to manage communication between machines, as one would in a multi-player game.