Enforce Script Syntax – DayZ

From Bohemia Interactive Community
Revision as of 16:37, 2 May 2019 by RVn (talk | contribs) (memory leak example :p also should be covered above now)
Jump to navigation Jump to search

Template:Stub


Enforce Script is the language that is used by the Enfusion engine first introduced in DayZ Standalone. It is a Object-Oriented Scripting Language (OOP) that works with objects and classes and is similar to C# programming language.

Basics

Code blocks

Code block is bordered by curly brackets and defines a scope. Variables defined inside scope are accessible only from inside of it.

Scope example

void Hello()
{
	 int x = 2; // First declaration of x
}

void Hello2()
{
	 int x = 23; // Different x not related to the first one
}

Nested scope

void World()
{
    int i = 5;
 
    // Following i is in a nested scope (scope of the for loop)
    // Therefore we get an error because multiple declaration is not allowed
    for (int i = 0; i < 12; ++i)
    {
    }
}

Scope statements

void Hello()
{
    if (true)
    {
        // This is code block for the if branch
        int x = 2; // First declaration of x
    }
    else
    {
        // This is code block for the else branch
        int x = 23; // This will currently throw a multiple declaration error - while this should change for future enfusion script iterations, it might stay like this in DayZ. To circumvent this, define the x above the if statement or use different variables.
    }
}

Program structure

Enfusion script consists of classes and functions. All code must be declared inside a function.

class MyClass
{
    void Hello()
    {
        Print("Hello World"); // ok
    }
}
  
void Hello()
{
    Print("Hello World"); // ok
}
  
Print("Hello World"); // this code will be never executed, and should be caught by compiler as unexpected statement

Variables

Variables are defined by type and name.

void Test()
{
    // declare 
    int a;
      
    // assign 
    a = 5;
      
    // initialize 
    int b = 9;
}

Functions

Functions are basic feature of Enfusion script. Function declaration consist of return value type, function name and list of parameters.

  • Function can be declared in global scope or inside class declaration
  • Function parameters are fixed and typed (cannot be changed during run-time, no variadic parameters)
  • Function can be overloaded
  • Keyword 'out' before parameter declaration ensures, that the parameter is passed by reference (you can passed more than one value from a function this way, can be used only in native functions)
  • Enfusion script supports default parameter
void MethodA()  // function with no parameters and no return value
{
}

int GiveMeTen() // function with no parameters which returns integer value
{
    return 10;
}
 
void GiveMeElevenAndTwelve(out int val1, out int val2, int val3) // function with 2 of the parameters passed as reference 
{
    val1 = 11;
    val2 = 12;
    val3 = 13;
}
  
void PrintNum(int a = 0) // example of function with default parameter
{
    Print(a);
}

void MethodB()
{
    int ten = 0;
    int eleven = 0;
    int twelve = 0;
    int thirteen = 0;
     
    ten = GiveMeTen();
     
    // function "GiveMeElevenAndTwelve" sets values of "eleven" and "twelve" variables, 
    // because "val1" and "val2" parameters are marked with "out" keyword, 
    // but value of "thirteen" variable is not changed, because third parameter is not marked as "out" and "val3" 
    // behaves only like a local variable of "GiveMeElevenAndTwelve" function
    GiveMeElevenAndTwelve(eleven, twelve, thirteen);
  
    Print(ten); // prints "ten = 10"
    Print(eleven); // prints "eleven = 11"
    Print(twelve); // prints "twelve = 12"
    Print(thirteen ); // prints "thirteen = 0"
  
    PrintNum(); // function "PrintNum" has default parameter, so its ok to call with empty brackets, it prints "a = 0"
    PrintNum(7); // prints "a = 7"
}

float Sum(float a, float b) // function with two float parameters which return float value
{
    return a + b;
}

float Sum(int a, int b)	// overloaded Sum function which uses int parameters instead
{
    return a + b;
}

  
void PrintCount(TStringArray stringArray) // function with one "TStringArray" object parameter which returns no value
{
    if (!stringArray) return; // check if stringArray is not null
  
    int count = stringArray.Count();
    Print(count);
}

Comments

/*
Multi
line
comment
*/
  
void Test()
{
    Print("Hello"); // single line comment
}

Constants

Constants are like variables but read only. They are declared by const keyword.

const int MONTHS_COUNT = 12;
  
void Test()
{
    int a = MONTHS_COUNT; // ok
    MONTHS_COUNT = 7; // err! you cannot change constant!
}

Operators

Operator Priority: Priority of operators is similar to C language, more info.

Arithmetic Operators

Operation Symbol
Add +
Subtract -
Multiply *
Divide /
Modulo %

Assignments

Operation Symbol
Assign value to variable =
Increment variable by value +=
Decrement variable by value -=
Multiply variable by value *=
Divide variable by value /=
Increment variable by 1 ++
Decrement variable by 1 --

Relational (conditional)

Operation Symbol
More than value >
Less than value <
More or equal to the value >=
Less or equal to the value <=
Equal ==
Not equal !=

Others

Category -
Logical &&, ||
Bitwise &, |, ~
String +
Shift <<, >>
Assignment =
Indexing []
Negation !

Types

Primitive Types

Type name Range Default Value
int from −2,147,483,648 to +2,147,483,647 0
float from ±1.401298E−45 to ±3.402823E+38 0.0
bool true or false false
string - "" (empty string)
vector see float (0.0,0.0,0.0)
void - -
class - null
typename - null

Strings

  • Strings are passed by value, like primitive types
  • Can be concatenated by + operator
  • Strings are initialized and destroyed automatically
  • Strings can contain standardized escape sequences. These are supported: \n \r \t \\ \"
void Method()
{
    string a = "Hello";
    string b = " world!";
    string c = a + b;
     
    Print(a); // prints "Hello"
    Print(b); // prints " world!"
    Print(c); // prints "Hello world!"
}

Vectors

  • Vectors are passed by value, like primitive types
  • Vector values are accessible by [, ] operator, like static arrays
  • Vectors are initialized and destroyed automatically
  • Vector can be initialized by three numeric values in double quotes e.g. "10 22 13"
void Method()
{
    vector up = "0 1 0"; // initialized by values <0, 1, 0>
    vector down; // vector "down" has now default value <0, 0, 0>
  
    down = up;
    down[1] = -1; // change Y value of vector "down"
  
    Print(up); // prints <0, 1, 0>
    Print(down); // prints <0, -1, 0>
}

Objects

  • Objects in enforce script are references and are passed by reference
  • All member functions and variables are public by default. Use 'private' keyword to make them private
  • 'autoptr' keyword before object variable declaration ensures that compiler automatically destroys the object when the scope is terminated (e.g. function call ends)
class MyClass
{
    void Say()
    {
        Print("Hello world");
    }
}
  
void MethodA()
{
    MyClass o; // o == null
    o = new MyClass; // creates a new instance of MyClass class
    o.Say(); // calls Say() function on instance 'o'
    delete o; // destroys 'o' instance
}
  
void MethodB()
{
    // if you type autoptr into declaration, compiler automatically does "delete o;" when the scope is terminated
    autoptr MyClass o; // o == null
    o = new MyClass; // creates a new instance of MyClass class
    o.Say(); // calls Say() function on instance 'o'
}
  
void MethodC()
{
    MyClass o;
    o = new MyClass;
    o.Say(); 
    // This function doesn't delete the object, which causes memory leak
}
  
void UnsafeMethod(MyClass o) // Method not checking for existence of the input argument
{
    o.Say();
}
  
void SafeMethod(MyClass o)
{
    if (o)
    {
        o.Say();
    }
    else
    {
        Print("Hey! Object 'o' is not initialised!");
    }
}
  
void MethodD()
{
    autoptr MyClass o;
    o = new MyClass;
     
    SafeMethod(o); // ok
    UnsafeMethod(o); // ok
  
    SafeMethod(null); // ok
    UnsafeMethod(null); // Crash! Object 'o' is not initialised and UnsafeMethod accessed it!
}

Example of this & super

class AnimalClass
{
    void Hello()
    {
        Print("AnimalClass.Hello()");
    }
};
  
class HoneyBadger: AnimalClass
{
    override void Hello()
    {
        Print("HoneyBadger.Hello()");
    }
  
    void Test()
    {
        Hello();    // prints "HoneyBadger.Hello()"
        this.Hello(); // 'this' refers to this instance of object, so same as line above, prints "HoneyBadger.Hello()"
        super.Hello(); // refers to base(super) class members, prints "AnimalClass.Hello()"
    }    
}

Enums

Enumerators are set of named constant identifiers.

  • enums have int type
  • enum item value can be assigned in definition, otherwise it is computed automatically (previous item value plus one)
  • enum can inherit from another enum (item value continues from last parent item value)
  • enum name used as type behaves like ordinary int (no enum value checking on assign)
enum MyEnumBase
{
    Alfa = 5,       // has value 5
    Beta,           // has value 6
    Gamma            // has value 7
};
enum MyEnum: MyEnumBase
{
    Blue,          // has value 8
    Yellow,           // has value 9
    Green = 20,    // has value 20
    Orange        // has value 21
};
  
void Test()
{
    int a = MyEnum.Beta;
    MyEnum b = MyEnum.Green;
    int c = b;
  
    Print(a);   // prints '6'
    Print(b);   // prints '20'
    Print(c);   // prints '20'
}

Typenames

Typename is primitive type that contains type definition.

  • Typename variable can be initialized by name of type directly
  • Only known and fully defined types can be assigned ( not the forward declaration, which is a placeholder for later definition )

Templates

Enfusion script has template feature similar to C++ Templates, which allows classes to operate with generic types.

  • Generic type declaration is placed inside <, > (e.g. "class TemplateClass<class GenericType>" )operators after template class name identifier
  • Enfusion script supports any number of generic types per template class
class Item<Class T>
{
    T m_data;
    void Item(T data)
    {
        m_data = data;
    }
    void SetData(T data)
    {
        m_data = data;
    }
    T GetData()
    {
        return m_data;
    }
    void PrintData()
    {
        Print(m_data);
    }
};
  
void Method()
{
    Item<string> string_item = new Item<string>("Hello!"); // template class Item declared with type "string". In Item<string> class, all "T"s are substituted with 'string'
    Item<int> int_item = new Item<int>(72); // template class Item declared with type "int". In Item<int> class, all "T"s are substituted with 'int'
 
    string_item.PrintData(); // prints "m_data = 'Hello!'"
    int_item.PrintData(); // prints "m_data = 72"
}

Arrays

Static Arrays

  • Arrays are indexed from 0
  • Arrays are passed by reference, like objects
  • Static arrays are initialized and destroyed automatically
  • Size of static arrays can be set only during compilation time
  • Elements are accessible by array access operator [ ]
void MethodA()
{
    int numbersArray[3]; // declaring array of int with size 3
  
    numbersArray[0] = 54;
    numbersArray[1] = 82;
    numbersArray[2] = 7;
 
    int anotherArray[3] = {53, 90, 7};
 }
  
const int ARRAY_SIZE = 5;
  
void MethodB()
{
    int numbersArray[ARRAY_SIZE]; // declaring array of int with size of value of ARRAY_SIZE constant
     
    numbersArray[0] = 54;
    numbersArray[1] = 82;
    numbersArray[2] = 7;
    numbersArray[3] = 1000;
    numbersArray[4] = 324;
}
 
void MethodC()
{
    int size = 3;
    int numbersArray[size]; // err! size static array cannot be declared by veriable!
}

Dynamic Arrays

  • Dynamic arrays support change of size at runtime by inserting/removing array items
  • Dynamic arrays are provided through 'array' template class
  • Dynamic arrays are passed by reference
  • Dynamic Arrays are objects and therefore they must be created and destroyed like objects, so don't forget to use "autoptr" or delete operator!
  • Elements are accessible by "Get" function or by array access operator [ ]
  • There are already defined typedefs for primitive type arrays:
    • array<string> = TStringArray
    • array<float> = TFloatArray
    • array<int> = TIntArray
    • array<class> = TClassArray
    • array<vector> = TVectorArray
void Method()
{
    autoptr TStringArray nameArray = new TStringArray; // dynamic array declaration, "TStringArray" is the same as "array<string>"
     
    nameArray.Insert("Peter");
    nameArray.Insert("Michal");
    nameArray.Insert("David");
  
    string name;
    name = nameArray.Get(1); // gets second element of array "nameArray"
    Print(name); // prints "name = 'Michal'"
  
    nameArray.Remove(1); // second element is removed
    name = nameArray.Get(1); // gets second element of array "nameArray"
    Print(name); // prints "name = 'David'"
  
    int nameCount = nameArray.Count(); // gets elements count of array "nameArray"
    Print(nameCount); // prints "nameCount = 2"
}

Control Structures

Control structures work very similar to c# or c/c++ languages.

Conditional structures

If statement

void Method()
{
    int a = 4;
    int b = 5;
  
    if (a > 0)
    {
        Print("A is greater than zero!");
    }
    else
    {
        Print("A is not greater than zero!");
    }
     
    if (a > 0 && b > 0)
    {
        Print("A and B are greater than zero!");
    }
     
    if (a > 0 || b > 0)
    {
        Print("A or B are greater than zero!");
    }
  
    // 'else if' example
    if (a > 10)
    {
        Print("a is bigger then 10");
    }
    else if (a > 5)
    {
        Print("a is bigger then 5 but smaller than 10");
    }
    else
    {
        Print("a is smaller then 5");
    }
}

Switch statement

Switch statement supports switching by numbers, constants and strings.

void Method()
{
    int a = 2;
  
    switch(a)
    {
        case 1:
            Print("a is 1");
        break;
  
        case 2:
            Print("a is 2"); // this one is called
        break;
         
        default:
            Print("it's something else");
        break;
     }
  
    // using switch with constants
    const int LOW = 0;
    const int MEDIUM = 1;
    const int HIGH = 2;
  
    int quality = MEDIUM;
    switch(quality)
    {
        case LOW:
            // do something
            break;
  
        case MEDIUM:
            // this one is called
            // do something
            break;
  
        case HIGH:
            // do something
            break;
    }
  
    // using switch with strings
    string name = "peter";
    switch(name)
    {
        case "john":
            Print("Hello John!");
            break;
  
        case "michal":
            Print("Hello Michal!");
            break;
  
        case "peter":
            Print("Hello Peter!"); // this one is called
            break;
    }
}

Iteration structures

For

The for loop consists of three parts: declaration, condition and increment.

void Method()
{
    // this code prints
    // "i = 0"
    // "i = 1"
    // "i = 2"
    for (int i = 0; i < 3; i++)
    {
        Print(i);
    }
}
  
// this function print all elements from dynamic array of strings
void ListArray(TStringArray a)
{
    if (a == null) return; // check if "a" is not null
  
    int i = 0;
    int c = a.Count();
  
    for (i = 0; i < c; i++)
    {
        string tmp = a.Get(i);
        Print(tmp);
    }
}

Foreach

Simpler and more comfortable version of for loop.

void TestFn()
{
    int pole1[] = {7,3,6,8};
    array<string> pole2 = {"a", "b", "c"};
    auto mapa = new map<string, int>();
    mapa["jan"] = 1;
    mapa["feb"] = 2;
    mapa["mar"] = 3;
   
    // simple foreach iteration
    foreach(int v: pole1) // prints: '7', '3', '6', '8'
    {
        Print(v);
    }
   
    // foreach iteration with key (if you iterate trough array, key is filled with array index)
    foreach(int i, string j: pole2) // prints: 'pole[0] = a', 'pole[1] = b', 'pole[2] = c'
    {
        Print("pole[" + i + "] = " + j);
    }
   
    // map iteration, with key and value
    foreach(auto k, auto a: mapa) // prints: 'mapa[jan] = 1', 'mapa[feb] = 2', 'mapa[mar] = 3'
    {
        Print("mapa[" + k + "] = " + a);
    }
   
    // map iteration with just value
    foreach(auto b: mapa) // prints: '1', '2', '3'
    {
        Print(b);
    }
}

While

void Method()
{
    int i = 0;
  
    // this code prints
    // "i = 0"
    // "i = 1"
    // "i = 2"
  
    while (i < 3)
    {  
        Print(i);
        i++;
    }
}

Classes and Objects

Classes can be seen as a blueprint of an object. An object is an instance of a class. A class can have more than one object.

Basic Class Example

class MyClass 
{
	private string _test;

	void MyClass() 
	{
		// Constructor that will be called when class gets instantiated
	}

	void myMethod()
	{
		// Some code here
	}
	
	string getTest() 
	{
		return _test;
	}

	string setTest(value) 
	{
		_test = value;
	}
}

Instantiate and Use

Use the new operator to instantiate a class. You can access methods or attributes by using dot notation.

Example

 class MyClass {
      int attributeExample = 1;
 
     public MyClass() {
          // Constructor
     }
 
     public string myMethod() {
          return "This is a string return";
     }
 }
 
 MyClass myClassI = new MyClass();
 myClassI.attributeExample // 1
 myClassI.myMethod // "This is a string return"


Modded class

Modded class is used to inject inherited class into class hierarchy without modifying other scripts, which is required for proper modding

  • Modded class behaves like class inherited from original class (you can use super to access original class)
  • When modded class is declared, it will be instanced instead of original class everywhere in the script
  • When several modded classes are modding the same vanilla class, the next modded class will instead inherit of the latest modded class, which enables mod compatibility
// original
class ModMe
{
    void Say()
    {
        Print("Hello original");
    }
};
 
// First mod
modded class ModMe  // this class automatically inherits from original class ModMe
{
    override void Say()
    {
        Print("Hello modded One");
        super.Say();
    }
};

// Second mod
modded class ModMe  // this class automatically inherits from first mod's ModMe
{
    override void Say()
    {
        Print("Hello modded Two");
        super.Say();
    }
};
 
void Test()
{
    ModMe a = new ModMe(); // modded class ModMe is instanced
    a.Say(); // prints 'Hello modded Two' , 'Hello modded One' and 'Hello original'
}