User Tools

Site Tools


arma2:scripting:basics_of_sqf

Basics of SQF

Chapter 1: Introduction

Everyone with a basic understanding of makings mission in Armed Assault (ArmA) knows how important it can be to make scripts. Scripts can be used to check if a group of units is alive, to update real time markers on the map or to supply waypoints for an AI group. Since Operation Flashpoint (OFP) there are two types of scripts, sqs and sqf. With the release of ArmA, the SQF files take up dominant position over SQS ones. dominant position over SQS ones. In short the advantages of SQF:

  • SQF is faster.
  • SQF can be used to make functions and scripts.
  • SQF improves the program flow.
  • SQF is structured, SQS is rarely as structured as SQF.

“Consider also that SQF works perfectly in ArmA. If you want something more “fine” performance wise, go for sqf, else you may keep with SQS. For example, if you want something to be checked every 30 seconds, you will have no gain at all with SQF, if you want something being executed every 0.001 seconds, go with SQF. If you find yourself comfortable with SQF programming flow structure, go with SQF forever. There is at least one exception: scripts to be executed every # seconds and at the end of a dropped particle (particle array), still should be SQS.” - Mandoble

The next few chapters will take you deeper into SQF syntax so that you get a basic understanding of how to implement it.

Chapter 2: Getting started

As most of you probably know, SQS and SQF syntax is plain text so we can make use of any good text editor. After opening up the file we're going to make a SQF file which can be opened by the ArmA engine. It's the same as you did before with .sqs, only now replace it with .sqf and save it as.

If you don't know how this works try placing the filename.sqf between quotes and “save it as”. It would look like this: “test.sqf” - , this let's text editor create a file with any extension.

Now it's time to load up the mission editor in ArmA, choose Rahmadi and save the mission as “test”. A mission folder will be created by ArmA, the default path is: PROFILE_NAME/missions/test.Intro, place the test.sqf script in the folder. It's also possible to place scripts in subdirs, for example a folder named “scripts” in the mission folder. In campaigns you can even put the scripts into a global folder, the script will then be accessible in any mission.

Chapter 3: Rules of SQF syntax

In OFP you had the SQS type which basically didn't care about the layout of your code. With the “goto” statement you was able to go from top to bottom and back. The use of the semicolon “;” at the end of each line wasn't required to make a working script. One thing not to overlook while typing SQF syntax is to use the curled braces { } instead of quotes “”. The new syntax should be clean to read, which is a big plus over SQS which could be messy and was allowed to be hardly readable with you skipping though the whole file following goto's. So to sum it up, in SQF syntax:

  • Use the curled braces { } instead of the quotes “” in statements, not for commands.
  • Use the semicolon at the end of each line to indicate where a statement ends.
  • Do not use the “goto” statement.

If you apply these basic rules of SQF syntax you will keep the bugs at bay. If you refuse to be consistent, please do not use SQF as you'll run into a large amount of bugs, because of missing semicolons and curled braces. Once you're used to these new rules, you'll appreciate what these rules implied, basically: a well structured and easily readable script. It's often a good idea to include comments of your own to make your script even easier to read. To insert comments into the code you can't use the semicolon. You can use two methods:

comment "This is a comment";
 
// this is a comment (prefered method by many);
 
/*
block of text
block of text
block of text
*/

Termination

Finally we'll discuss how to end scripts. One way to do this is by using the command Terminate Its syntax:

scriptName = [parameters] execVM "yourname.sqf";
Terminate scriptName;

Here, scriptName is the variable you've assigned to the script when activating it. An example of termination:

_plane = [2] execVM "createPlanes.sqf";
sleep 10; // Stop the script Terminate _plane;

More frequently you'll be exiting the script from inside a loop structure. We can do that with the command exitWith. The basic syntax:

if (CONDITION) exitWith { CODE TO EXECUTE };

As you can see this is an easy command to use when you want to exit script. See it as the replacement for something like the following SQS syntax many may have used back in OFP or are still using today.

hint "start check";
_i = 0;
#loop
hint format ["_i = %1/100",_i];
~3
_i = _i + 1;
?(_i == 100): hint "exiting"; exit;
goto "loop"

This SQS syntax can be replaced by two kinds of SQF syntax. Check below for the examples. Note that it's easier to use the script without the exitWith command.

hint "start check";
for [{_i=0},{_i<=100},{_i=_i+1}] do
{
   hint format ["_i = %1/100",_i];
};
hint "exiting";

There are cases where using exitWith could turn out handy. Think of situations where you have a script and want to check in the middle if everything is going as planned.

soldier1 doMove getPos soldier2;
soldier1 setDir 230;
soldier2 doWatch soldier1;
doStop soldier3;
if (!alive soldier4) exitWith { hint "Mission Failed"; "1" objStatus "FAILED"; };
"1" objStatus "DONE";
hint "Mission Completed";
soldier4 setDir 150;
soldier4 doMove getPos soldier2;

As you can see the script gets terminated when a condition, the death of soldier4, is true. That concludes the part about SQF Syntax Rules.

Chapter 4: Basic SQF statements

The advanced scripters will be aware of statements like “if”,“while” and “for”, but I'm sure that there are people out there who've never heard of them. Chapter 4 is a chapter devoted to explaining the basic structure of these statements and how they can be used to achieve certains goals in a script. To advanced SQS scripters this chapter can be of use when trying to annihilate all bugs during the conversion of SQS to SQF.

if

Everyone uses the word “if” in daily life, for example in “if you drink milk, your health will improve”. After analysis of the word “if” we come to the conclusion that it's something like this: if a condition is met, action will be taken. In ArmA it's exactly the same, but written in a format your PC can understand and interpret.

if (CONDITION) then
{
	STATEMENT;
};

Note the semicolons used in this expression. One to end the expression and one for every statement in the “if-then” construction. The CONDITION has to be set according to your needs. You want something to happen after the condition has been met. I'll try to clarify it with a small example.

comment "Check if there is time left.";
_timeLeft = 1;
if (_timeLeft < 2) then
{
   hint "Hurry up, only a few minutes remaining!";
};

The first line of the script is just a comment. You can use these to tell what you're going to do in the next few lines. On the 2nd line you see that we give the variable “_timeLeft” a value of 1. We'll use this on line 3 to decide if a hint is to be displayed. Line 3 shows the beginning of he “if-statement”, if the variable has a value lower than 2, then do what's inside the curled braces. On line 6 you'll see the code to execute when the “if-statement” is true, meaning that the CONDITION has been met. In this case a hint will be displayed telling the player to hurry up. With only the “if-statement”, not much is possible. We still can't keep one code running for a longer period and it would take another “if-statement” to check if the “_timeLeft” variable is actually higher than two. But, there is a method we can use to check for this by using “else”.

comment "Check if there is time left.";
_timeLeft = 3;
if (_timeLeft < 2) then
{
   hint "Hurry up, only a few minutes remaining!";
} else {
   hint "Plenty of time left."; };

With the addition of the “else”-part we have the option to execute some code if the CONDITION is NOT met. You can see that “_timeLeft” recieved a value of 3, which means that it isn't lower than 2. So the hint that you should hurry up will not be displayed. But, as we included the “else”, there is something else that will be triggered by the CONDITION not met. A hint will be displayed telling the player he has enough time left.

switch

There is also a command you can use to check one variable and then execute the right code. You can do that with the “switch”-structure.

switch (VARIABLE) do
{
   case VALUE:
   {
      STATEMENT;
   };
   default
   {
      STATEMENT;
   };
};

This probably doesn't tell you much and it might be hard to actually understand what can be done with this. So take a look at the following script.

comment "Check the color.";
_color = "green";
switch (_color) do
{
   case "red":
   {
      hint "color: red";
   };
   case "blue":
   {
      hint "color: blue";
   };
   default
   {
      hint "color: blue nor red";
   };
};

In this example the switch statement checks the value of “_color”, which is set to “green”. The script will then take a look at each case and look if the case is equal to the variable. If it is, the code is executed. You can make as many cases as you want, just keep in mind that if you got a whole lot of them, it's probably better to look for a different solution. Note that each case tests a value and should have the “:” to ensure it works. I've also included the “default” case in the example. You can use it to execute code if no case matches the variable, as in the above script.

while

With “if” and “switch” statements a lot is possible, but to write advanced scripts you need different structures. You need structures that can run multiple times, to replace the “loops” you used in SQS made with the “goto”. In SQF you can use the following loops: “while”,“for” and “forEach”. Let's start with “while”, first the standard code.

while {CONDITION} do
{
   STATEMENT;
};

In words, while the condition is true, the STATEMENTS between the curled braces are being executed over and over again. So with it, you can do some maths for example. It's time for an example to clarify this loop.

_x = 0;
while { _x <= 10 } do
{
   _x = _x + 1;
};
hint "We did some counting.";

With this piece of code we can raise the variable “_x” in steps until a maximum is reached. First, the variable gets the value 0. Then the while statement checks if the condition is met and 0 is indeed lower than 10. So we're going through the STATEMENT part where the variable is raised by 1. Then the condition is checked again and if the condition is met, the script will run another time. Until “_x” becomes 11 and fails the condition to run the script which results in the loop stopping and the script continuing on the next line, displaying the hint.

for

In ArmA the “while” structure can be used to do various things, but can become quite frustrating. As you see we had to set the “_x” variable before the “while” loop started. It would be easier to do this in the loop itself, to keep things clean. With the “for” loop a lot is possible, it's a bit like the “while” loop, but with more parameters. Time to examine the “for” structure. Be aware: the “for” structure can only be executed 100000 by steps, without the STEP parameter unlimited times.

for [{BEGIN}, {CONDITION}, {STEP}] do
{
   STATEMENT;
};

As you can see this structure seems a bit more complicated then the “while” loop for example. It might be confusing to use in the beginning, but it can be a powerfull loop in a script. There are three parameters you have to set for the “for” loop. BEGIN is the starting value, the condition is checked every loop, the step is executed after each loop and will most likely raise your counter. Hopefully the next example will clear things up.

_x = 0;
for [{_p = 0},{_p < 10},{_p = _p + 1}] do
{
   _x = _p * 100 + _x;
};
hint format ["%1",_x];

This is probably one of the harder loops to understand and use. In the script I start with defining the variable “_x” to use in the loop, it has nothing to do with the loop structure. Then the “for” structure starts. Between the first pair of brackets is “_p = 0”, without a semicolon! “_p” will the variable that the “for” structure will increment. Next comes the “_p < 10” part which is the CONDITION for this loop. If true, the loop will continue running. The last part is the STEP to take each time the loop has been run once.

A step by step analysis:

  1. _p is set to 0
  2. _p is lower than 10, true
  3. The statement will be executed, the “_x = …” part.
  4. _p will be raised by 1
  5. _p, now 1 is lower than 10, true
  6. Execute statement
  7. Increase _p by one

forEach

What remains is “forEach”, a really useful loop which can be used to shorten up large shunks of code.

{
   STATEMENT;
}
forEach ARRAY;

The forEach is really useful in a situation when you have an array and you want a specific code to run for every element of the array. An example to illustrate this:

{
   _x setDamage 1;
}
forEach [unit1,unit2];

For every unit in the array, in this case units 1 and 2, will get setDamaged. The value of “_x” is the selected index value in the array. So in the first loop it will be “_x = unit1”, the second “_x = unit2”. The “forEach” structure runs as often as the total number of elements.

Chapter 5: Implementation

After writing your script you want to implement it in your mission or cutscene. This can be done with either of the following commands:

call { [parameters] execVM "yourname.sqf"; }
or
variable = [parameters] execVM "yourname.sqf";

Notice that the code to execute SQF files is a little bit more complicated than SQS files. That is because ArmA still considers SQF as functions, which have to be stored in a variable or have to be executed. The call command executes code between the curled brackets. By using these commands you let the engine search a script named “yourscript”, you don't even have to type the subdir the script might be in. The script will search in sub and global folders if it can't find the script in the default folder. As we're using SQF syntax, the script has to be compiled first, this is automatically done when using “execVM”. Note that returning a value with this command won't work, to do this you'll to precompile the SQF as described on the Biki. You may have already noticed that you can give the script a few parameters between the [ ] brackets. This is very useful as you can use one script for multiple inputs. Assume that we have a script which will dynamically make waypoints for a unit with a complicated code. The script doesn't do this for a specific unit, but for a variable which gets set in the beginning of the script. We can let it run for hundreds of different units, if we call the script for every unit with the name of the unit as a parameter. Not clear? An example, call a script using the following code.

call { [soldier1,player] execVM "moveTo.sqf"; }

Now onto the script syntax, for this example I made a script which will let a designated unit run towards another unit. Both parameters, which unit (1) is to run to who (2), are inserted in the parameters field of the “execVM”. They can be collected in the script by using the methods in the script below. As you can see, what we pass to the script in the parameter field is actually an array. In the script we can access the parameters by selecting the element of the array “_this” (the parameters are stored in an array named “_this”) in which a specific value parameter is stored.

comment "moveTo.sqf";
_unit = _this select 0;
_unitPos = _this select 1;
_unit doMove position _unitPos;

As a result, “soldier1” will move to “player” after executing this script. By altering the parameters, or in this case the names of who moves to who, you can run the same script multiple times. Thus being able to use one script, to move 100 units to 100 units. Because the SQF syntax is compiled before being executed and compiled scripts being faster than uncompiled ones, this is good for the performance. This chapter was supposed to give you some insight into implementing SQF scripts. In short, use “execVM” to execute SQF syntax and remember that by using it no value can be returned.

Chapter 6: Techniques

There are a few techniques to make developing scripts and functions a lot smoother. That is because they have to be executed from A to B to C, unlike SQS syntax which made it possible to go from A to C to B. The consequence of this is that you must plan the structure of your script before you write one. This is untrue for small, but very true for larger scripts. That is why I'll write about a few things to take note of before you should start with your script.

If you have experience with SQS, you know that the “goto” was very useful at times. As this isn't possible in SQF syntax, probably making this syntax faster too, we'll have to think of other ways to accomplish things. In Chapter 8: SQS to SQF, I'll go deeper into this subject. Right now, I'm only going to address a few basic techniques required to plan and write a well structured script.

A good control flow, the sequence of your statements, loops etc, is required as written before. The first thing is to think about your problem. What problem do you want to solve? Let's assume that we have to following problem:

We want Private Armstrong to move to the enemy position and empty his magazine from 10m if he has ammo.

Let's cut it down into pieces we want to execute. This is always a smart idea, but your problem into pieces which are easy to execute. The only thing you have to do later, is to integrate all of it into one. It's good to get a good idea of what has to be done in order to solve the problem. In SQS you could get away with sloppy planning, or sloppy analysis of what has to be executed when, it was just a matter of some “goto”-ing. Now, you might even be forced to rewrite a script.

  1. Get control of Armstrong.
  2. Check if he can move.
  3. Order him to move to the enemy position.
  4. Check if he arrived.
  5. Check if he has ammo.
  6. If true, let him fire.

This might seem silly, and it probably is a bit silly. But it illustrates how to show your problem into subproblems which are pretty easy to solve. If we execute all of these steps in a sequence the result would work. Whereas if we let him fire, but forgot to check if he arrived at his destination you may find yourself in trouble. Now, don't get me wrong, for most of the scripts it is not required to think about the program flow, but for large scripts which can do a lot of once it is advised to work in pieces. Using multiple scripts and functions together would ensure that you work clean. Work in small pieces, it is common in the software development world that people create different parts and integrate them at the end.

Now another example, used to show how to split up parts of a problem and how to use multiple scripts/functions to solve one problem. First I'll descibe the problem, followed by an analysis then how I would solve the problem by making different scripts to work together.

Create dynamic waypoints and let a group follow them, every now and then they will use the radio to report position, status etc.

  1. Create dynamic waypoint
  2. Select a group and tell them to go to what waypoint.
  3. Check if the group is at the destination.
  4. In the meanwhile: Use radio to report position, status etc.
  5. Repeat from step 1.

If we merge all of these steps we solved the problem. As you can see this one is a bit harder. We want more checks, more loops in this script to solve the problem. To keep the code clean, two scripts could be made. One for the waypoint creation and movement of the group. A second to use the radio and inform the HQ of the status. The goal of this is to keep the structure of the scripts simple and to the point. In the end, the structure is going to be mostly loops in one, while delivering output is done in another script. The result is that we seperated the needed clean structure from output, and by giving parameters to the other script we can output some lines of code by using another script.

This is not something that guarantees 100% working scripts, nor do I allege that you can script something without dividing and splitting it. It's a method or technique I use while making scripts that are larger than 20 lines or so. Just to be able to debug and maintain a clean readable structure.

Chapter 7: Debugging & RPT LogFile

ArmA displays the error produced by the bug, accompanied by the actual bug. Most of the times, it's just a typo that needs to be fixed. In some cases the command is outdated, you forgot a semicolon or gave the wrong input for a command or function. The bug leads to the engine not being able to write the script and thus exiting while producing an ugly bug report. So now that you know what a bug is, let's take the time to learn how to prevent bugs.

As the structure of SQF has to be proper, we have an advantage fighting bugs. In the previous chapter, I elaborated on splitting scripts. This can help fighting bugs even more, because you can then easily isolate the problem. When scripting, always use tabs. These will help you find a double semicolon for example. And if possible, try to spread a code over multiple lines. Take a look at the following script, a script we discussed earlier, in two forms. One with tabs the other one without.

_x = 0;
for [{_p = 0},{_p < 10},{_p = _p + 1}] do
{
   _x = _p * 100 + _x;
};
hint format ["%1",_x];
 
_x = 0; for [{_p = 0},{_p < 10},{_p = _p + 1}] do {    _x = _p * 100 + _x; }; hint format ["%1",_x];

The second is shorter, the first is a lot easier to read, thus easier to debug. Also, note the tab before the statements in the “for” loop. Without it, it would be harder to see what is in the loop and which part is out. Not only these scripts, but also free more advanced text editors allow you to highlight different codes.

It is quite inevitable that once you will have to deal with a bug. In this part I'll give the BIS wiki description of the bugs. Then, I will write down a technique which can be used to eliminate what part of your script is producing the bug. So that you or other people can try to fix that part.

Most errors have been taken from Common Scripting Errors:

Generic Error in Expression
This error occurs when the type of data an operator is expecting does not match.
Example:

_myString = "The meaning of life is " + 42;

Invalid Number in Expression
This usually occurrs when an expression is left incomplete or malformed.
Example:

_myNumber = 2 + 3 +

Missing ;
The syntax cannot be compiled, the compiler needs a semicolon.
Example:

_myBug = "1 + 1"
_goTo = "Rest";

Zero Divisor
This error occurs when the variable you are trying to divide has a value of zero, or when you attempt to divide a variable by 0. It also occurs when you attempt to access an element of an array which does not exist.
Example:

_myVar = [1,2,3] select 15;

scalar bool array string 0xe0ffffef
If variables don't exist, their value as string is generally a “scalar bool array string 0xe0ffffef” error.
Example: </code cpp> hint format [“%1”, undefined_variable]; </code>

With the help of this list and the example you should be able to find most of the bugs in a few seconds, because the error gives the erroneous lines. If you fail to fix it fast. You might want to isolate your bug, so that you can analyze the line or few lines that are producing the bug. To do this, gradually remove parts of you script. Let's say you have a script of 50 lines. The error is given at line 23. Remove the last 25 lines as they are probably not producing the error. Cut these lines out and paste them into another document, be sure not to get them lost. Now you can gradually try to cut out lines and check if the error is still reported. Most of the times this too much work and simple taking a good look at it helps to. Remember though that the error can be in one of the previous lines. Of you miss a semicolon, it can be that the error is given at the next line. This is because the compiler detects that he missed the error at the next line. Be aware of this.

RPT LogFile

The RPT Log can be found here: arma2.rpt

Now what exactly is the RPT file? The RPT LogFile contains all the errors the engine returned to you. This means that the file is very handy to sort out problems. If for example your script contains multiple errors, you'll only see one of them if they are returned in a short period. With the RPT you will be able to take a look at all the errors returned by the engine.

The Log contains information about the file generating the error, the line which is erroneous and the error itself. So once you can find the Log, don't panic if you think that you missed a bug message. You can always find it in the RPT file.

Chapter 8: SQS to SQF

I'm sure that at this point you have a pretty good idea what SQF is, but that you have a good script in SQS syntax. So it's time to take a look at converting SQS syntax to SQF syntax. It can take a lot of effort, especially with complex scripts, but it certainly is doable. The problem is not only the size of the script, but moreover the way how the SQS syntax is written. If for example, you used a lot of “gotos” it can be pretty hard to convert it into SQF syntax. What might help, as pointed out in Chapter 6, is to cut the script down into smaller pieces and in the good order. This good order will help you with selecting the right loops and loops in loops. Right, time for an example.

SQS:

_x = 0;
_array = [unit1,unit2,unit3];
#loop
?(_x > (count _array)): exit;
(_array select _x) setDamage 1;
_x = _x + 1;
goto "loop"

This code is a bit sloppy and SQF forces you to use a better script and much shorter. If we keep in mind what the different loops did, “forEach” is the best solution to convert this script.

SQF:

_array = [unit1,unit2,unit3];
{
   _x setDamage 1;
}
forEach _array;

Shorter, without “goto”, easier to read if you understand the basics of SQF. So we did a good attempt at converting this to SQF. Now it's time for a complicated script to convert.

#loop
?(!(alive m2_1_gunner) AND !(alive m2_2_gunner)): m2_1 setdammage 1; m2_2 setdammage 1;
?(!(alive m2_4_gunner)): m2_4 setdammage 1;
?(!(alive m2_5_gunner)): m2_5 setdammage 1;
?(!(alive m2_6_gunner) AND !(alive m2_8_gunner)): m2_6 setdammage 1; m2_8 setdammage 1;
?(!(alive m2_10_gunner) AND !(alive m2_11_gunner)): m2_10 setdammage 1; m2_11 setdammage 1;
?(!(alive m2_12_gunner)): m2_12 setdammage 1;
?(!(alive m2_13_gunner)): m2_13 setdammage 1;
?(enemy_cleared==1): exit
~3
goto "loop"

The same script written in SQF syntax.

enemy_cleared = 0;
_gunArray = [m2_1,m2_2];
_menArray = [m2_1_gunner,m2_2_gunner];
_total = (count _menArray);
while { enemy_cleared != 1 } do
{
   for [{_i = 0},{_i < _total},{_i = _i + 1}] do
   {
      if (!(alive (_menArray select _i))) then
      {
         (_gunArray select _i) setDammage 1;
      };
   };
   sleep 1;
};

As you can see this script is of about the same length as the original SQS file. It is better structured though, which makes it easier to keep track of what happens. It could be improved a bit more, replacing the two arrays with only one array. Note that I only filled the arrays with two elements, it was good enough to illustrate how it would work. So the script is shorter for fewer M2s, this doesn't have to mean that it's faster or less CPU demanding.

arma2/scripting/basics_of_sqf.txt · Last modified: 2011-07-24 08:36 by snakeman