Jump to content
Sign in to follow this  
Double Doppler

Script efficiency

Recommended Posts

I am looking for ways to improve performance on scripts and functions overall.

Firstly, I heard functions are the way to go, as they are already loaded in the memory and do not have to be compiled every time they are called. But wont the loading of functions tax the memory? like variables?

Secondly, I noticed in some missions there is a technique to compiling/executing many files at once:

_funcs = [
"Script1.sqf",
"Script2.sqf",
"Script3.sqf",
"Script4.sqf",
"Script5.sqf"
];

for "_i" from 0 to (count _funcs -1) do {
_func= _funcs select _i;
call compile preprocessfilelinenumbers _func;
};

But is this just a case of EC (excessive cleverness)? I would have thought that the operations to do the math would have taken longer than procedurally compiling/executing the files at once.

Again, I look for tricks of the trade and common rules of thumb. I dont know whether basing the entire mission is functions is a good idea as they might take up memory, hence cause instability, as I earlier stated. I am learning so I am not 100% sure this is the case. Again, I am open all ears.

Share this post


Link to post
Share on other sites

I think you are overestimating the effect a few vars have on memory usage.

I'd say

- use functions if you plan on using/doing the same thing more than once

- preprocess scripts

Share this post


Link to post
Share on other sites

I wouldn't worry about memory usage at all. Look at this: https://dev-heaven.net/issues/25602#note-2. CBA and ACE memory usage for scripts is around 5.5MB only.

Processing is shared between scripts in Arma and one script being slow will slow down other scripts. Instead focus on making your scripts perform better. Using compile is a good way to do this. Instead of using execVM which preprocesses, compiles and spawn a script, do the first 2 steps before and just spawn the ready code when you need it:

//Bad if you do it multiple times
[] exexVM "myscript.sqf";
//Better
MYTAG_MyScript = compile preProcessFile "myscript.sqf";
//When you need to run it
[] spawn MYTAG_MyScript;

If you only execVM a script a single time during the entire mission it does not matter much that you use execVM.

I have never used a loop like that to prepare som functions. Usually I have several functions inside files and package my script into folders. If there is a lot of scripts inside a folder that will be used a lot I typically make a compile.sqf which call compile preProcessFile those scripts.

Share this post


Link to post
Share on other sites

Also, what is the reason for making local variables private to the file? I know about private variables being used in code scopes, but I noticed some missions declare the local variables private at the beginning of the file.

What's the use if the variable is automatically local to the file when using underscore? Is there a reason for this?

I remember this one group claimed it reduces de-sync, but I would have thought that had nothing to do with variables being private/local or global, as the ones in question were all running on the client.

Share this post


Link to post
Share on other sites

Yes. It is because of the concept of scopes. Imagine I start a loop and want to end it at some time. Let's say my condition is this:

while {_myVar} do {
 //...
}

I need to define _myVar before the while loop right. Inside the loop I can change _myVar to false when something is true:

_myVar = true;
while {myVar} {
  _numUnits = {isPlayer _x} count allUnits;
  if (_numUnits > 10) then {
     _myVar = false;
  } else {
     hint "Not enough players yet";
     sleep 5.0;
  };
};

You can see from inside the while loop I am able to modify the _myVar outside the loop. This also extends to functions. Say I have function PrintMsg and LogMsg and the one uses the other:


LogMsg = {
   _origMsg = _this select 0;
   //Prepend the fact that this is the script doing the logging
   _msg = format ["MySuperScript says: %1", _origMsg];
   diag_log _msg;
};

PrintMsg = {
   _msg = _this select 0;
   //Log message
   [_msg] call LogMsg;
   //Also show the player
   hint _msg;
};

If you:

["Welcome everybody"] call PrintMsg;

Then LogMsg is called with the ["Welcome everybody"]. LogMsg then adds some text and saves it in _msg. Which _msg? There is actually only one and that is the one in PrintMsg! So when the call returns the text "MySuperScript says: Welcome everybody" is displayed instead of the intented message.

By using the private statement you can tell Arma that the variables you are about to use are new variables and should be in a new scope. You can fix this like this:


LogMsg = {
   private ["_origMsg", "_msg"];
   _origMsg = _this select 0;
   //Prepend the fact that this is the script doing the logging
   _msg = format ["MySuperScript says: %1", _origMsg];
   diag_log _msg;
};

PrintMsg = {
   private ["_msg"];
   _msg = _this select 0;
   //Log message
   [_msg] call LogMsg;
   //Also show the player
   hint _msg;
};

Then the _msg in LogMsg will be another variable with the same name. You can't reach the _msg in PrintMsg directly anymore. You should always do this in your functions since may not know which variable names function calls higher up may use.

Global vs local has nothing to do with desync or the network.

Edited by Muzzleflash

Share this post


Link to post
Share on other sites

Interesting thread , i also would like to know about this compile and memory becuase i was thinking the other day ,when someone ask can i place 1000 objects in terrain will it lag, i think not , but then i thought about the compiled and how i did it wrong :0

i put in addon Init

thromp_exampledebris = compile preprocessFileLineNumbers "\thromp_example\scripts\debrisfly.sqf";

and in dammaged handler

dammaged = "_this call thromp_exampledebris;";

I wondered ,will the engine know its already compiled , will it compile it every time someone place the wall , what effect will this have.

In the end i just realise a Module is best for the

thromp_exampledebris = compile preprocessFileLineNumbers "\thromp_example\scripts\debrisfly.sqf";

But what are thoughts about the Mess at first ? any known facts what will happen when this is done badly like i did ?

Share this post


Link to post
Share on other sites

Very interesting. Thanks for question and thanks for answers.

Share this post


Link to post
Share on other sites

preProcess and preProcessFileLineNumbers: Take a text file and removes line with // and the parts between and including /* */. It also applies and #define, #ifdef and similar. The result is a string, simply the text that was in the file without comments and preprocessor definitions.

compile: Takes a string and turns it into code.

Thromp:

If it perfectly fine to compile the script into some variable a single time and then call it from elsewhere as many times as you like. The engine doesn't "know" anything. If you use call it expect you to perform it on code. What you are doing is perfectly fine and I don't know what "mess" you are talking about.

Are you talking about putting it inside unit init lines or similar?

Edited by Muzzleflash

Share this post


Link to post
Share on other sites

Thanks, but I understand the concept of private variables. What I am trying to say is:

Why do some scripters declare local variables private when they are outside a scope? Like at the beginning of an sqf file.The only scope I could find them in was the file itself. They were not declared private in a statement of any sort. What is the use in this? Doesn't local work enough? They are declaring local variables private, and they are in no scope whatsoever except the file itself.

I will provide an example to further make my point easier:

Say for example I had a script named "Script.sqf". Inside at the very beginning it we have:

[color="Red"]private ["_parameter1","_parameter2"];[/color]
_parameter1 = _this select 0;
_parameter2 = _this select 1;

If(...

As you can see here the variables are declared private at the start of the file. The only scope they are in is the file itself. I do not know why they do this but is there some sort of performance advantage. Perhaps they are paranoid about locality so much that they make local values local twice?

If you look in warfare gamemodes like say Benny's warfare for example you will see there is a statement at the beginning of the script declaring local variables private. This is what I am questioning.

Share this post


Link to post
Share on other sites

That is because the file does not contain functions. It is a function. Continuing my example here are 2 ways to define the function LogMsg:

//Example_A.sqf
LogMsg = {
   private ["_origMsg", "_msg"];
   _origMsg = _this select 0;
   //Prepend the fact that this is the script doing the logging
   _msg = format ["MySuperScript says: %1", _origMsg];
   diag_log _msg;
}; 

To get that function I do:

call compile preProcessFile "Example_A.sqf";

Another way:

//Example_B.sqf
private ["_origMsg", "_msg"];
_origMsg = _this select 0;
//Prepend the fact that this is the script doing the logging
_msg = format ["MySuperScript says: %1", _origMsg];
diag_log _msg;

The get that you do:

LogMsg = compile preProcessFile "Example_B.sqf";

Share this post


Link to post
Share on other sites

It's a paranoia/habit and doesn't serve any real purpose. Though, one usage it does have; you can declare a variable private, but not assign a value to it in the main scope, but since it's declared you can use the var in an inner scope.

Share this post


Link to post
Share on other sites

Thromp:

What you are doing is perfectly fine and I don't know what "mess" you are talking about.

Are you talking about putting it inside unit init lines or similar?

The Mess i percieved was :

the addon was a wall and its config ad a line that i showed , My thinking was , that for each wall placed the Init was started and there for the compile was done for every wall ,

Of course in the end there would be only one Thromp_..debris , but i wondered , what is cost that if someone placed 1000 walls and each wall had in its INIT to comile the thromp_expldebris , of course in my mind i fixed by making a moduel for the init and compile , but was there anything to fix i was wondering in terms of Cpu usage on the Gamestart by all this walls with same init?

Share this post


Link to post
Share on other sites
The Mess i percieved was :

the addon was a wall and its config ad a line that i showed , My thinking was , that for each wall placed the Init was started and there for the compile was done for every wall ,

Of course in the end there would be only one Thromp_..debris , but i wondered , what is cost that if someone placed 1000 walls and each wall had in its INIT to comile the thromp_expldebris , of course in my mind i fixed by making a moduel for the init and compile , but was there anything to fix i was wondering in terms of Cpu usage on the Gamestart by all this walls with same init?

Sry I misunderstood. It is definitely much better to simply compile it once instead of doing it in every initline.

Share this post


Link to post
Share on other sites

Speaking of private variables, I did a little test on one of my functions and found out:

if you do not declare them at the beginning but in a scope, they are already private to that scope unless you redefine them at the beginning. Maybe this is why the put the private variables at the start.

If this is the case it would be nice if someone could update the BIKI article with this information, to avoid future confusion.

Edited by Double Doppler

Share this post


Link to post
Share on other sites

They put the private variables at the top of the file primarily to avoid overwriting similarly named variables "higher up" in the call hieararchy like I demonstrated with _msg.

You can also use it prepare a variable that you will use later, but before you do assign it in an inner more scope:

private ["_assignInnerUseLater"];

if (true) {
  //In inner scope now
  _assignInnerUseLater = "Hello world";
};

//Out again
hint _assignInnerUseLater;

Without the private definition the hint would not work. Unless you called this and some place higher up someone used a variable named _assignInnerUseLater in which case it would override it and the hint would work - however when you return to the function higher up it might not expect you to modify that variable.

I am pretty sure the examples you are referring to are using it mostly for the first reason.

--Update

Try this:

1. Create new mission. Insert a player unit.

2. Create the file 'test_msg.sqf' in your mission folder containing this

private ["_msg"];
_msg = _this select 0;
//Log it
[_msg] call test_log;
//Tell player
hint _msg;

3. Create file 'test_log.sqf' in your mission folder containing this

_msg = format ["WARNING: %1", _this select 0];
diag_log _msg;

4. Create a trigger

Condition: true

On Act: test_log = compile preProcessFile "test_log.sqf"; test_msg = compile preProcessFile "test_msg.sqf";

5. Create a radio trigger

On Act: ["Friendly non-warning message"] call test_msg;

Start mission, activate radio trigger. You should get "WARNING: Friendly non-warning message" which is not the message test_msg intended, however, test_log corrupted it by overwriting the non-privatized variable.

Try putting:

private ["_msg"];

at the top of 'test_log.sqf' and run it again.

This is by the far the most often reason why they put private at the top of files.

Edited by Muzzleflash

Share this post


Link to post
Share on other sites
I am looking for ways to improve performance on scripts and functions overall.

Best advice i can give is DONT OVER SCRIPT

In most mission making - what you start out to do changes along the way "as you learn more , as things didnt work as you would have expected - good or bad-"

Use _variables as much as you can. that way the code can be reused.

the less IFs & Whiles the better.

Alot of scripts i look at i can do a slight change to the style of code and remove 50% of it.

Not 100% on this but i do it anyway as it seems logical

if i have a

IF ((_THIS) && (_THAT) && (_THEN) && (_WHAT) && (_WHO)) THEN {hint "FOR GOD SAKES PLEASE DO SOMETHING"};

I look at the code and try to work out which one is the least likly to be true -

Lets say its _THEN.

My code would then be

IF (_THEN) THEN

{

IF ((_THIS) && (_THAT) && (_WHAT) && (_WHO)) THEN {hint "FOR GOD SAKES PLEASE DO SOMETHING"};

};

If its FALSE it only has to check 1 variable not 5

If TRUE then it still has all 5

even tho it dont sound like its much processing power it all adds up

--------------------------------------------------------------------------------

Learn to use setVariable and getVariable

_veh = _this select 0;

_cargo = _this select 1;

_veh setVariable ["CARGO", _Cargo, true];

_Cargo = _veh getVariable "CARGO";

_veh setVariable ["CARGO", nil, true];

-- rgo,true]; - only if it needs to be public

False if you only need only the PC that ran the setVariable to use the getVariable

I like to write things to work randomly and

setVariable and getVariable save me having to write extra code "LOTS"

such as

Ascript.sqf


//   1/ making sure a script is not already running

_veh = _this select 0;
if (!isnil {(_veh getVariable "CARGO")}) exitwith {hint "im already running"};

//   2/ linking scripts to events without scripts linked;

waituntil {(!isnil {(_veh getVariable "CARGO")})};
_Cargo = _veh getVariable "CARGO";
hint "WE HAVE CARGO";
_Cargo attachto [_veh, [-0.04,-1.65,.66]];
sleep 2;
_delivery_spot = _Cargo getVariable "CARGO"; 
_veh domove _delivery_spot;
waituntil {(_veh distance _delivery_spot < 20)};
_Cargo attachto [_veh,[0,-7,-.8]];
sleep .1;
Detach _Cargo; 
_veh setVariable ["CARGO", nil, true];
hint "CARGO IS DELIVERED";

Anotherscript.sqf

if (!isserver) exitwith {};
_Cargo = _this select 0;
_delivery_spots = [house1,house2,house3];
_delivery_spot = (_delivery_spots select (floor(random (count _delivery_spots)))); 
_Cargo setVariable ["CARGO", (position _delivery_spot), true];

waituntil {(count (nearestObjects [_Cargo, ["Car"], 19]) > 0)};
_vehs = nearestObjects [_Cargo, ["Car"], 20];
_veh = (_vehs select 0);

//   3/ triggering an event 

_veh setVariable ["CARGO", _Cargo, true];

if you had a container at waypoint 3 running "Anotherscript.sqf" with _nil = [this] execVM "Anotherscript.sqf";

and 3 trucks running "Ascript.sqf" with _nil = [this] execVM "Ascript.sqf";

in there init.

the first truck to WP3 picks up the container , drops it off then continues on with its waypoints.

THIS IS JUST AN EXAMPLE OF SOME OF THE THINGS YOU CAN DO WITH setVariable and getVariable , UNTESTED , OFF THE TOP OF MY HEAD JUST NOW.

then continues on with its waypoints.

Dont script things you dont need to.

domove will override a waypoint until the domove position is reached

then it will return to its waypoints.

so there is no need for anymore code.

The BIS Arma Engine is Rather Good.

Check out -=BattleZone=- to see what can be achieved

with just a few "setVariable and getVariable"s and letting the BIS Arma Engine do the rest.

Hope this helps someone a bit

Zonekiller

Edited by Zonekiller

Share this post


Link to post
Share on other sites
I wouldn't worry about memory usage at all. Look at this: https://dev-heaven.net/issues/25602#note-2. CBA and ACE memory usage for scripts is around 5.5MB only.

Plus all BIS functions of the functions module.

We do things a little bit different though. All CBA, BIS and ACE functions (plus some other things) are compiled only once (and if you connect to a server in MP a recompile is scheduled again, but only once for each server).

So basically all functions are made persistent in uiNamespace untill the user closes the game again (saves quite some processing time (some seconds) when everything is compiled and the next mission starts).

Edit:

@Zonekiller

I have no idea who came up with that "putting variables into braces" thingie.

For example:

if ((_var1) && (_var2)) ...

is the same as

if (_var1 && _var2)....

or

waituntil {(!isnil {(_veh getVariable "CARGO")})};

it is the same as:

waituntil {!isnil {_veh getVariable "CARGO"}};

Now what is easier to read ?

Xeno

Share this post


Link to post
Share on other sites

Basically, since any script you make might be used in various ways or re-used later for different missions, it's best to always just private your variables.

For example:

init.sqf

_noPilots = (isNil "pilot1" && isNil "pilot2");
call compile preprocessFile "enemyscript.sqf";
if (_noPilots) then {hint "no friendly pilots available!"};

enemyscript.sqf

private ["_noPilots", "_noTanks", "_infantryCount"];
_noPilots=true;
_noTanks=true;
_infantryCount = 50;
if (random 1 > 0.5) then {_noPilots=false;};
if (random 1 > 0.5) then {_noTanks=false;};
if (_noPilots && _noTanks) then {_infantryCount = _infantryCount + 50;};
// do some more stuff

With the private you can be 100% sure that your _noPilots variable in enemyscript.sqf does not overwrite the one from init.sqf. If you don't want to keep track of private variable names (which is one of the reasons we use them in the firstplace), you need to use always use the private command if you want to be safe.

As for stuff like efficiency with "simplifying" your if conditions, you really shouldn't worry about it. In fact, generally in programming languages if checks only evaluate as much as they need to. That said, it's a good reason not to call any functions that actually do anything more than just return a value from within an if condition.

If you want to not make heavy scripts, just make sure you don't have any long/infinite loops and you should be mostly good to go, at least for commonly used stuff.

Edited by galzohar

Share this post


Link to post
Share on other sites

well i find it easier to read with the braces

--------------------------------------------------

if (({"alive" == lifeState _x

&&

east countside list _trigger unitsBelowHeight 10 > capture_units

&&

resistance countside list _trigger unitsBelowHeight 10 == 0

&&

west countside list _trigger unitsBelowHeight 10 == 0} count list _trigger unitsBelowHeight 10 > 0)

&&

getmarkercolor _marker != "colorred" && _ammo && time > _timeout + _hold) then

Vs

if (({("alive" == lifeState _x)

&&

(east countside (list _trigger unitsBelowHeight 10) > capture_units)

&&

(resistance countside (list _trigger unitsBelowHeight 10) == 0)

&&

(west countside (list _trigger unitsBelowHeight 10) == 0)} count (list _trigger unitsBelowHeight 10) > 0)

&&

(getmarkercolor _marker != "colorred") && (_ammo) && (time > (_timeout + _hold))) then

@Xeno

if i had

-- (getmarkercolor _marker != "colorred") && _ammo && (time > (_timeout + _hold))) then

i would see that as an error every time i opend the script because it stands out.

-

------------------------------------------------------------------------------

its a good habbit to use braces

as

_num = 100 + 10 * 5;

_num == 550

_num = 100 + (10 * 5);

_num == 150

---

if ((time > timeout) && ((alive player) or (alive vehicle player))) then {this one reads the &&}

differs from

if (time > timeout && alive player or alive vehicle player) then {this one will bypass the &&}

I know you and I both know this , But just to keep it clear to the people learning

Some Things DO NEED braces.

Edited by Zonekiller

Share this post


Link to post
Share on other sites

I think he meant avoiding stuff like this:

_num = ((100) + ((10) * (5)));

Share this post


Link to post
Share on other sites

Zonekiller, nobody says that you don't need braces at all. But sometimes they make no sense at all.

So again, why do you (and many others) put variables into braces ?

Like

_somebool = false;
_someotherbool = true;
if ((_somebool) && !(_someotherbool)) then {...

And concerning your example...

This:

if (time > timeout && (alive player || alive vehicle player)) then {this one reads the &&}

is exactly the same as yours...

if ((time > timeout) && ((alive player) or (alive vehicle player))) then {this one reads the &&}

Xeno

Share this post


Link to post
Share on other sites

As it highlights the bool elements clearly.

Share this post


Link to post
Share on other sites
So again, why do you (and many others) put variables into braces ?

1/ consistency

ie

(getmarkercolor _marker != "colorred") && _ammo && (time > (_timeout + _hold))

Vs

(getmarkercolor _marker != "colorred") && (_ammo) && (time > (_timeout + _hold))

2/ my script and i will write it the way i find it easier to read

this is OFF TOPIC anyway

make a new thread "PICK THE SHIT OUT OF ZONEKILLERS CODE if you need to.(You will find alot of it in the forums)

Im just trying to pass on a few things i learnt , if someone uses it im sure they

will put braces where they wish to have them.

The commands and the idea was my point

I am looking for ways to improve performance on scripts and functions overall.

Dont know why it wont let me post this as a link ,

Code Optimization

ArmA 2 scripting code optimization tips.

Written it twice? Put it in a function

Pre-compilation by the game engine can save up 20x the amount of time processing, even if the initial time is slightly lengthened. If you've written it twice, or if there is a kind of loop consistently being compiled (perhaps a script run by execVM), make it into a function:

funcvar = compile preprocessFileLineNumbers "filename.sqf";

preprocessFileLineNumbers

The preprocessFileLineNumbers command remembers what it has done, so loading a file once will load it into memory, therefore if wanted to refrain from using global variables for example, but wanted a function precompiled, but not saved, you could simply use:

call compile preprocessFileLineNumbers "file";

Remembering the only loss of performance will be the compile time of the string returned and then the call of the code itself.

Length

If any script or function is longer than around 200-300 lines, then perhaps (not true in all cases by all means) you may need to rethink the structure of the script itself, and whether it is all within scope of the functionality required, and if you could do something cleaner, faster and better.

Use Indentation

Write clean code, use indentation which helps you fix 90% of the average errors in your code and makes it easy to read and even understand.

Constants

Using a hard coded constant more than once? Use preprocessor directives rather than storing it in memory or cluttering your code with numbers. Such as:

a = _x + 1.053;

b = _y + 1.053;

And

_buffer = 1.053;

a = _x + _buffer;

b = _y + _buffer;

Becomes:

#define BUFFER 1.053

_a = _x + BUFFER;

_b = _y + BUFFER;

This also allows quick modifying of code; with the obvious loss of dynamics, but in that case it isn't a constant is it.

Loops

These first two loop types are identical in speed (+/- 10%), and are more than 3x as fast the proceeding two loop types.

for "_y" from # to # step # do { ... }

{ ... } foreach [ ... ];

Where as these two loops are much slower, and for maximum performance, avoided.

while { expression } do { code };

for [{ ... },{ ... },{ ... }] do { ... }

waitUntil can be used when you want something to only run once per frame, which can be handy for limiting scripts that may be resource heavy.

waitUntil

{

expression;

};

Note: you can also use sleep in this command, like:

waitUntil

{

sleep 1;

expression;

};

Which effectively makes it not hog all your CPU cycles if you use many simultaneous waitUntil commands.

Threads

The game runs in a scheduled environment, and there are two ways you can run your code. Scheduled and non scheduled.

Depending on where the scope originates, determines how the code is executed. Scheduled code is subject to delays between reading the script across the engine, and execution times can depend on the load on the system at the time.

Some basic examples:

â– 

Triggers are inside what we call the 'non-scheduled' environment;

â– 

All pre-init code executions are without scheduling;

â– 

FSM conditions are without scheduling;

â– 

Event handlers (on units and in GUI) are without scheduling;

â– 

Sqf code which called from sqs-code are without scheduling.

The 0.3ms delay (not 3ms)

The 0.3ms delay is a delay introduced in ArmA2 for scheduled environments to prevent script overload during game play (assuredly due to the occurrences in ArmA1). The 0.3ms is experienced between statements and upon finishing a statement, will proceed down the schedule until returning to the start to go through the loop again. As I always seem to be able to explain things in code, the behavior as far as I can explain it is the following:

while (true)

{

{

execute1Statement (_x);

sleep 0.3ms;

} foreach SCRIPTS;

}

You can therefore see that the delay between statement execution inside your script is dependent on n scripts (or threads). In this way you should refrain from creating to many threads in your code to stop the system from scaling to the point functionality is seriously degraded. This information is subject to change as BIS may change things in the latest beta releases.

When am I creating new threads?

Using the spawn/execVM/exec commands are creating small threads within the scheduler for ArmA2 (verification from a BIS DEV for specifics is needed here), and as the scheduler works through each one individually, the delay between returning to the start of the schedule to proceed to the next line of your code can be very high (in high load situations, delays of up to a minute can be experienced!).

Obviously this problem is only an issue when your instances are lasting for longer than their execution time, ie spawned loops with sleeps that never end, or last a long time.

Avoid O(n^2)

Commonly you may set up foreach foreach's. 'For' example:

{

{ ...} foreach [0, 0, 0];

} foreach [0, 0, 0];

This example is of the order (n^2) (3^2 = 9 iterations). For arrays that are twice as big, you will run 4 times slower, and for arrays that are 3 times as big you will run 9 times slower! Of course, you don't always have a choice, and if one (or both) of the arrays is guaranteed to be small it's not really as big of a deal.

Deprecated/Slow Commands

Adding elements to an array. Set is around 2x faster than binary addition.

_a set [count _a,_v];

Instead of:

_a = _a + [_v];

Removing elements from an array

When FIFO removing elements from an array, the set removal method works best, even if it makes a copy of the new array.

arrayx set [0, objNull];

arrayx = arrayx - [objNull];

CreateVehicle Array

It is highly recommended to use a standard of createVehicle array rather than the older (deprecated version) createVehicle. It is up to 500x faster than its older brother.

Measuring Velocity Scalar

Sure we can just use Pythagorean theorem to calculate the magnitude from a velocity vector, but a command native to the engine runs much faster (over 10x faster) than the math.

vector distance [0, 0, 0];

Works for 2D vectors as well.

Getting object positions

getPosASL and getPosATL are 2.11x faster than regular getPos

If your feeling self-conscious and want an AGL solution (ie identical to that of getPos):

private "_pos";

_pos = getposATL player;

if (surfaceIsWater _pos) then

{

_pos = getposASL player;

};

It is still 25% faster than its getPos twin.

How to test and gain this information yourself?

There is a few ways to measure the information and run time durations inside ArmA 2, mostly using differencing of the time itself. The following code setup allows different ways to retrieve the information (chat text, rpt file, clipboard)

_fnc_dump =

{

player globalchat str _this;

diag_log str _this;

//copytoclipboard str _this;

};

_t1 = diag_tickTime;

// ... code to test

(diag_tickTime - _t1) call _fnc_dump;

or this

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 realtime 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 confortable 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. As we progress you'll read about some techniques to use in scripting and a bit about debugging. Then it's time to convert your SQS syntax to SQF syntax. At the end I'll give a short recap. For example scripts you should watch the editor depot of www.ofpec.com as I'm planning to develop some script soon. I already released a function, gridRCoord.sqf which translates a [x,y,z] position to for example Be13 map coordinates. Knowledge you should already possess to be able to read this article is limited. With the examples everything should be explained deeply enough to be clear to even a beginner. Any programming experience is welcome and handy to understand SQF and the article.

--------------------------------------------------------------------------------

Chapter 2: Getting started

[top]

As most of you probably know, SQS and SQF syntax is plain text so we can make use of any plain text editor. Open up wordpad, notepad or any program you favor to start writing text. Remember, it's better to use notepad than MS Word. 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 notepad 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: My Documents/ArmA Other Profiles/PROFILE_NAME/missions/test.Intro, place the test.sqf script in the folder. It's also possible to place scripts in subfolders, 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

[top]

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

[top]

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:

_p is set to 0

_p is lower than 10, true

The statement will be executed, the "_x = ..." part.

_p will be raised by 1

_p, now 1 is lower than 10, true

Execute statement

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.

Basic syntax of the loops has been copied from the BIS wiki.

--------------------------------------------------------------------------------

Chapter 5: Implementation

[top]

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 subfolder 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

[top]

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.

Get control of Armstrong.

Check if he can move.

Order him to move to the enemy position.

Check if he arrived.

Check if he has ammo.

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.

Create dynamic waypoint

Select a group and tell them to go to what waypoint.

Check if the group is at the destination.

In the meanwhile: Use radio to report position, status etc.

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

[top]

You may have heard it "debugging is a programmers worst nightmare", in fact. Most of the rookie programmers spent more time debugging than scripting. Of course this is far from productive so I advice everyone to prevent bugs. Should the need arise to hunt bugs, I'll explain later in this chapter where to look for them. Let's define a bug first before going into the prevention and later fixing.

Copied from wikipedia: "A software bug is an error, flaw, mistake, failure, or fault in a computer program that prevents it from behaving as intended."

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. You can use the 30 day free trial of UltraEdit and the required UE SQF Highlight from Kegetys' website to let it work. I use NotePad++, but there is no highlight pack for SQF syntax, yet.

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:

hint format ["%1", undefined_variable];

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 errorneous lines. If you fail to fix it fast. You might want to isolate your bug, so that you can analyse 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:

C:/Documents and Settings/user/Local Settings/Application Data/ArmA/ARMA.rpt

You might have to change the settings to see hidden maps / files in the configuration (MapOptions) of Windows. 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 errorneous 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

[top]

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. The following script is one I used for "Operation Lightning", you had to make a beach landing with several M2s. Back in OFP soldiers would shoot at an enemy unmanned MG until it would explode. To counter this I wrote a script to check if the M2 gunners were killed, if yes, I would let the empty M2 explode. You may have already seen that this script can be improved a lot.

#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.

--------------------------------------------------------------------------------

Chapter 9: Conclusion

[top]

This concludes the tutorial about SQF syntax. Hopefully the SQF syntax has became at least a bit clearer than before reading this tutorial. You should now be able to write tiny to medium sized SQF scripts. Writing more complex scripts requires a lot of work and probably even more debugging. The learning process and development of tutorial can go faster if you use several techniques discussed in the tutorial.

Remember that SQS isn't as outdated as everyone tells you. It can work faster, but most of the times it's a lot easier and less time consuming to develop a script in SQS. It's mostly for functions and complex scripts that SQF syntax is needed. I hope that with this tutorial you can at least understand the basic elements of SQF syntax.

Good luck! Happy scripting!

Edited by Zonekiller

Share this post


Link to post
Share on other sites

Please sign in to comment

You will be able to leave a comment after signing in



Sign In Now
Sign in to follow this  

×