From SQF to Enforce Script – Arma Reforger

From Bohemia Interactive Community
Revision as of 13:54, 21 June 2022 by Lou Montana (talk | contribs) (Add OOP link)
Jump to navigation Jump to search

Welcome to Enfusion!

While Arma 3 is powered by SQF, Arma Reforger uses Enforce Script as language, which is syntactically close to C#.

SQF is a succession of Scripting Commands processed in a certain order to give you a certain access or reference to the engine objects themselves (e.g player) - learn more about SQF here.

It is easy to access, read and process at the cost of performance.

Enforce Script is an Object-Oriented Programming language (OOP language) which as this designation suggests is based on objects.

See Object Oriented Programming Basics for an introduction to the OOP approach.

The language is way closer to the engine and is inspired by C++, which allows scripters more possibilities, but also more responsibilities - a script can make or break one's game experience.

It is a stricter language that allows for a wider, stronger interaction with the engine.


Similarities

Let's start with the similarities between the languages… because they are very different, the list will actually be a short one.

SQF Enforce Script
// comments /* comment blocks */ private _booleanValue = true; private _floatValue1 = 5; private _floatValue2 = 5.5; private _stringValue = "Hello there";
// comments
/*
	comment blocks
*/

bool booleanValue = true;
float floatValue1 = 5;
float floatValue2 = 5.5;
string stringValue = "Hello there";

And that's it! As this example already shows, even the variable declaration syntax has changed. Now, let's see the main differences on first sight!


Main differences

If-Then-Else

Definitely the most important point of this document. SQF being keyword-based, it needs to "bridge" the "if" condition to the "then" value, using the then keyword. Enforce Script uses if-else structure like any recent language.

SQF Enforce Script

if (alive player) then { hint "I am alive!"; };

if (myPlayer.GetHealth() > 0) // no more 'then'!
{
	Print("I am alive!");
};

The same applies for other code structures (while-do, switch-do, for-do, etc), see Code Flow below.

The else part supports else if (note: not elseif) statements:

SQF Enforce Script

if (alive player) then { hint "I am alive!"; } else { if (damage player > 0.9) then { hint "That's a lot of damage"; }; };

if (myPlayer.GetHealth() > 0) // no more 'then'!
{
	Print("I am alive!");
}
else if (myPlayer.GetHealth() < -90)
{
	Print("That's a lot of damage");
}

Code Flow

for

SQF Enforce Script

// There are two ways to write a for-loop in SQF: for "_i" from 0 to 10 do { }; for [{ _i = 0 }, { _i < 10 }, { _i = _i + 1 }] do { };

// Enforce Script has only one type of for loop
// statement, condition and step are separated by semicolons
for (int i; i < 5; i++) // i is already initialised to 0
{
};

foreach

SQF Enforce Script

// forEach loop in SQF is very convenient by selecting current index and current variables automatically { hint str _forEachIndex; // current index of the array is accessible by _forEachIndex hint _x; // _x is automatically selected variable representing iterated element in the array } forEach _items;

// foreach's syntax is changed and follows today's standards
// the code to be run is placed after the keyword
// beware of the syntax: currentIndex and currentElement are divided with ",",
// but currentElement and array used in by ":"
foreach (int currentIndex, string currentElement : items)
{
	Print(currentIndex);
	Print(currentElement);
};

// or, without the index need
foreach (string currentElement : items)
{
	Print(currentElement);
};

while

SQF Enforce Script

// Unlike other flow structures, while condition is written between {} brackets while { _i < 10 } do { _i = _i + 1; };

// Condition check in while loop is done in regular () brackets
while (i < 10)
{
	i++; // this will increment i by 1
};

switch

SQF Enforce Script

switch (i) do { case 0: {}; // if i is 0 code in brackets is executed, and switch is exited automatically case 1: {}; default {}; // default does not use ":" like case does };

switch (i)
{
	case 0: { continue; };	// continue exits the current scope
							// but keeps evaluating other cases below
	case 1: { break; };		// the break keyword is used to exit the switch or loop structure
	default: {};			// default does use ":" like case does
};

Others

SQF Enforce Script

// SPECIAL CASES i = if (isScript) then {1} else {0}; // if-then-else returning value, not possible in Enforce Script if (isScript) exitWith {}; // if that breaks from current scope // lazy evaluation: by default, all statements in condition field are evaluated // to prevent this behaviour the second statement should be wrapped in code brackets {} // wrong as "_value > 10" will be evaluated // if (!isNil "_value" && _value > 10) then if (!isNil "_value" && { _value > 10 }) then { hint "_value exists and is greater than 10"; };

//SPECIAL CASES
// an if statement can avoid brackets for only one line of code
if (isScript)
	break; // this will exit a while loop
if (isScript)
	return 0; // this will exit the current method and return 0


// "value.GetValue() > 10" will not be evaluated if "value != null" returns false
if (value != null && value.GetValue() > 10)
{
	Print("value exists and is greater than 10");
}

Case Sensitivity

Everything is case-sensitive in Enforce Script, unlike SQF:

SQF Enforce Script

private _myValue = "Hello there"; hint _MYVALUE; // hints "Hello there" IF (true) THEN { hint "it's true!"; }; if (_myValue == "HELLO THERE") then { // will work };

string myValue = "Hello there";
Print(MYVALUE); // error: MYVALUE is undefined

IF (true) // error: "IF" is an unknown operator. "if" is the proper casing
{
	Print("it's true!");
}

if (myValue == "HELLO THERE")
{
	// will NOT work - "Hello there" is different from "HELLO THERE"
};

Typed Variables

In SQF, a variable can change its type on the go according to value assignation. In Enforce Script, a variable has one type and this type cannot be changed during the variable's lifetime.

SQF Enforce Script

private _myValue = "Hello there"; // _myValue is a string _myValue = 5; // _myValue is now a floating point number _myValue = true; // _myValue is now a boolean // etc

string myValue = "Hello there";		// myValue is a string
myValue = 5;						// error: cannot convert an int to string
myValue = true;						// error: cannot convert a bool to string

Position

A position is the location of an object. A position is composed of X, Y and Z values, X being the West → East axis, Y being the Floor → Sky axis, and Z being the South → North axis. A position is called origin in Enfusion.

Real Virtuality uses the [X, Z, Y] format (a vector pointing up being [0, 0, 1]) whereas Enfusion uses { X, Y, Z } (a vector pointing up being { 0, 1, 0 }).

Note that in Enfusion the coordinate system is left-handed.
SQF Enforce Script

private _northVector = [0, 1, 0]; private _westVector = [-1, 0, 0]; private _upVector = [0, 0, 1];

vector northVector = "0 0 1";
vector westVector = "-1 0 0";
vector upVector = "0 1 0";

Array

In SQF, an array is a list that can contain any type of data including sub-arrays, and can be expanded at will. In Enforce Script, there are two types of array: static and dynamic; also, an array can only contain one type of data.

SQF Enforce Script

private _array = []; _array pushBack 0; _array pushBack [1]; _array pushBack "2"; // etc _array set [0, 1]; // sets the first element to 1 // array duplication private _array2 = +_array;

array<int> myArray = {};	// dynamic array
myArray.Insert(0);			// OK
myArray.Insert(true);		// OK - true is 1
myArray.Insert("2");		// error: an array cannot contain a different type
myArray[0] = 1;

// array duplication
array<int> duplicate = {};
duplicate.Copy(myArray);

// new array type: static (size) array
int myArray[3] = { 0, 0, 0 };
myArray[0] = 1;
myArray[1] = 2;
myArray[2] = 3;

Data Types

Including vectors, enums, sets, classes of course and others! Find all the new data types on their dedicated page.

SQF Enforce Script

private _position = [50,50,0]; // an array private _number1 = 42; // a Number, a.k.a float private _number2 = 5.5; // a Number, a.k.a float _number1 = _number1 + 1; _number2 = _number2 - 1; private _hashmap = createHashMap; _hashmap set [1, "oops"]; _hashmap set [2, "two"]; _hashmap set ["two", true]; _hashmap set [1, "one"]; private _value = nil; if (isNil "_value") then { hint "_value is not defined" }; private _value = objectParent player; if (isNull _value) then { hint "player has vehicle" };

vector position = { 50, 0, 50 };
int number1 = 42;
float number2 = 5.5;
_number1++;
_number2--;

map<int, string> hashmap = new map<int, string>(); // types are set in stone
hashmap.Insert(1, "oops");
hashmap.Insert(2, "two");
// no Enscript equivalent
hashmap.Set(1, "one"); // Set() is to update a value - it can create it too, but Insert() is faster

set<string> stringSet = new set<string>();
stringSet.Insert("value1");
stringSet.Insert("value2");
stringSet.Insert("value1"); // returns false as value1 already exists

// there is no isNil equivalent in Enforce Script as a variable is either defined or not
// its content can still be null as seen below

MyClass myInstance;
if (myInstance == null)
	Print("myInstance is null");

// alternatively
if (!myInstance)
	Print("myInstance is null");

Object-Oriented Programming

SQF is a scripting language based on sequences of expressions set in scripts. Enscript is Object-Oriented Programming (OOP) meaning that the code is placed inside declared objects.

The basics of OOP can be found in Object Oriented Programming Basics.
SQF Enforce Script

// in-script code declaration - normally one would have to declare it in CfgFunctions private _sum = { params [ ["_value1", 0, [0]], ["_value2", 0, [0]] ]; _value1 + _value2; }; private _result = [5, 3] call _sum;

// a method is a class' function
class MyClass
{
	int Sum(int value1, int value2)
	{
		return value1 + value2;
	}

	// a different method signature, yet named the same
	string Sum(string value1, string value2)
	{
		return value 1 + value2;
	}
}

// further in code
MyClass myInstance = new MyClass();
int result = myInstance.Sum(5, 3);
string result = myInstance.Sum("Hello ", "there");
string result = myInstance.Sum("Hello ", 3); // does not work - no such string/int signature, only string/string
It is worth noting that Enforce Script does not benefit from previous titles' Scheduler, therefore wildly creating new threads is not recommended! See Enforce Script's example below for Callqueue's usage.
SQF Enforce Script

hint "Thread A 1/2"; [] spawn { hint "Thread B 1/2"; sleep 1; hint "Thread B 2/2"; }; hint "Thread A 2/2";

class MyClass
{
	void PrintMessage()
	{
		Print("Thread A 1/2");
		GetGame().GetCallqueue().CallLater(PrintOtherMessage, 1000); // in milliseconds
		// thread Thread_PrintOtherMessage(); // thread usage is not recommended
		Print("Thread A 2/2");
	}

	void PrintOtherMessage()
	{
		Print("CallQueue later");
	}

	void Thread_PrintOtherMessage()
	{
		Print("Thread B 1/2");
		Sleep(1000);
		Print("Thread B 2/2");
	}
}

// further in code
MyClass myInstance = new MyClass();
myInstance.PrintMessage();


See Also