Operators (ZScript)

From ZDoom Wiki
Jump to navigation Jump to search
Note: This page is for ZScript. For ACS operators see here.


This is a list of all operators supported by ZScript and their descriptions.

Assignment operators

= (assignment)

int foo = 5;

Assignment can be combined with arhitmetic operators as follows:

  • += (assignment + addition)
  • -= (assignment + subtraction)
  • *= (assignment + multiplication)
  • /= (assignment + division)
  • %= (assignment + modulo)

Assignment can be combined with bitwise operators as follows:

  • &= (bitwise AND assigment)
  • |= (bitwise OR assigment)

Arithmetic operators

+ (addition)

int foo = 1 + 1; // foo = 2
int bar = foo + 1; // bar = 3

- (subtraction)

int foo = 1 - 1; // foo = 0
int bar = foo - 1; // bar = -1

* (multiplication)

int foo = 2 * 10; // foo = 20
int bar = foo * 10; // bar = 200

** (power)

int foo = 2 ** 8; // foo = 256

/ (division)

Note: If both operands are integer numbers, the result of division will be an integer number, truncated (not rounded). At least one of the operands has to be a double value to get a decimal result.

int foo = 10 / 2; // foo = 5
int foo1 = 5 / 2; // foo = 2

double bar = 5 / 2; // bar = 2.0 because 2.5 was truncated
double bar1 = 5.0 / 2; // bar1 = 2.5
double bar2 = 5 / 2.0; // bar2 = 2.5

Round (round to closest), floor (round down) and ceil (round up) can be utilized on a float-point division to set the result to the closest suitable integer number:

double d = (5 / 3.0); // d = 1.666...
int foo = round(5 / 3.0); // foo = 2
int foo1 = ceil(5 / 3.0); // foo1 = 2
int foo2 = floor(5 / 3.0); // foo2 = 1

% (modulo)

Returns a remainder after dividing integer numbers.

int foo = 5 / 2; // foo = 1 because 5 = 4 * 2 + 1 where 1 is the remainder

This operator can be used to perform something on a fixed time basis in a function that is being called every tic:

override void Tick()
{
  Super.Tick();
  if (Level.maptime % TICRATE == 0)
  {
    // This will be called every second while the game isn't paused
  }
}

(See Tick)

++ (incrementation)

Increases the value by 1.

int foo = 1;
foo++; // foo = 2

Note: placing the ++ operator before the value is possible as well: ++foo. If this is done while comparing the value to another value, the incrementation will be performed befor comparison. Compare:

int foo = 5;
bool isBigger = foo++ > 5; // isBigger is false
int foo = 5;
bool isBigger = ++foo > 5; // isBigger is true

-- (decrementation)

Decreases the value by 1.

int foo = 1;
foo--; // foo = 0

Note: placing the -- operator before the value is possible as well: --foo. If this is done while comparing the value to another value, the decrementation will be performed befor comparison. Compare:

int foo = 5;
bool isSmaller = foo-- < 5; // isSmaller is false
int foo = 5;
bool isSmaller = --foo < 5; // isSmaller is true

Relational operators

These operators are used in IF/ELSE, FOR, WHILE statements to check for conditions

== (exactly equal)

Checks if two operands are equal to each other:

if (myNumber1 == myNumber2)
{
  // True if both numbers are the same
}
if (myPointer1 == myPointer2)
{
  // True if both pointers point to the same object
}

Using != flips the check.
With floats and doubles, outside of specific circumstances ~== should be used instead, as floating-point error and precision loss means values from different calculations will rarely match exactly.

~== (approximately equal)

Checks if two operands are approximately equal to each other.

With floats and doubles, it checks if they're within 1/65536.0 (0.0000152587890625) distance of each other. Note that this check should almost always be used instead of ==, as due to the way floating-point numbers work, in particular error and precision loss, means they'll rarely match exactly.

if (myDouble1 ~== myDouble2)
{
  // True if the difference between the two values is less than 0.0000152587890625
}

With strings, ~== performs a case-insensitive check:

string str1 = "Adam";
string str2 = "ADAM";
if (str1 ~== str2)
{
  // This check will pass
}
if (str1 == str2)
{
  // In contrast, this check will fail because the two strings use
  // different capitalization, and '==' is case-sensitive
}

> (greater)

if (myNumber1 > myNumber2)
{
  // True if myNumber1 is greater than myNumber2
}

< (less)

if (myNumber1 < myNumber2)
{
  // True if myNumber1 is less than myNumber2
}

>= (greater or equal)

if (myNumber1 >= myNumber2)
{
  // True if myNumber1 is greater than or equal to myNumber2
}

<= (less or equal)

if (myNumber1 <= myNumber2)
{
  // True if myNumber1 is less than or equal to myNumber2
}

Logical operators

Logical operators are usually used in IF/ELSE, WHILE and FOR statements to combine conditions. Note: in case of multiple conditions, the statement will abort as soon as it encounters the first condition that isn't met. As such, stringing multiple conditions (while possibly detrimental to readability) will not increase the performance cost. The only exception is the ternary operator.

No operator

If an operator is skipped in a conditional check, the check is implicitly considered to be a boolean check:

if (myBool)
{
  // True if myBool is true
}

// This does the same thing:
if (myBool == true)
{
}

With pointers, it performs a null-check:

if (myPointer)
{
  // True if myPointer is not null
}

// This does the same thing:
if (myPointer != null)
{
}

With numeric values, it performs a zero-check:

if (myNumber)
{
  // True if myNumber is not 0
}

// This does the same thing:
if (myNumber != 0)
{
}

With String values it checks if the string is not empty:

if (myString)
{
  // True if myString is not ""
}

// This does the same thing:
if (myString != "")
{
}
Error.gif
Warning: The above check will work on strings but will NOT work on names unless a name was explicitly set to be empty. This is because when a String declared without a value, it's implicitly set to "", but when a name is declared, it's implicitly set to 'none'.


&& (logical AND)

Used to check if all conditions in a statement are true:

if (<condition1> && <condition2>)
{
  // True if both conditions are true
}
while (<condition1> && <condition2>)
{
  // loops as long as both conditions are true
}

|| (logical OR)

Used to check if at least one conditions in a statement is true:

if (<condition1> || <condition2>)
{
  // True if either condition is true
}
while (<condition1> || <condition2>)
{
  // loops as long as either condition is true
}

! (logical NOT)

Used to invert any condition in a conditional check:

if (foo != bar)
{
  // True if foo isn't equal to bar
}
if (<condition1> && !<condition2>)
{
  // True if condition1 is true but condition2 is false
}
if (!<condition1> && !<condition2>)
{
  // True if both conditions are false
}
if (!<condition1> || !<condition2>)
{
  // True if either condition is false
}
if (!(<condition1> && <condition2>))
{
  // True if either condition is false
}

This operator can also be used to null-check pointers and zero-check values:

if (myPointer != null)
{
  // True if myPointer is NOT null; this is the same as just 'if (mypointer)'
}
if (!myPointer)
{
  // True if myPointer IS null; this is the same as 'if (myPointer == null)'
}
if (!foo)
{
  // If foo is a string, this is true if foo is "".
  // If foo is a pointer, this is true if foo is null.
  // If foo is a numeric value, this is true if foo is 0.
}

? (ternary)

Essentially, syntactic sugar for an IF statement. The basic syntax requires ? and : and functions as follows:

<condition>? <result if true> : <result if false>

For example:

int foo = <condition>? 5 : 10; // if condition is true, foo = 5, otherwise foo = 10

Note, a ternary operator can only be used if both possible values are explicitly of the same type. For example, it's not possible to combine a string and a name value, both have to be either strings, or names:

// We can't use "none" here, because double quotes would turn 
// it into a string, whereas GetClassName returns a name,
// so we MUST use 'none' in single quotes:
name clsname = target != null? target.GetClassName() : 'none';

// Conversely, we can't use GetClassName() directly, because 
// it returns a name, not a string, so the result of
// GetClassName() first has to be cast to a string:
string clsnameStr = target != null? ""..target.GetClassName() : "None";

For actor pointers, null needs to be explicitly cast as Actor:

// This has a ~50% chance to set 'foo' to the calling
// actor's target; otherwise it'll be set to null:
Actor foo = random(0, 10) >= 5? target : Actor(null); //Just null won't be recognized

Note: nesting ternaries is possible...

int foo = <condition1>? 5 : <condition2>? 10 : <condition3>? 20 : <condition4>? 40 : 50;

...but not recommended. Not only does it reduce readability, it also increases performance cost because the engine has to go through every condition, even when only one of them can be true.

Notes on order of execution

When strung together, AND/OR operators follow several rules:

1. The conditions are always checked one by one, from left to right. This is why a condition like this is perfectly safe:

if (myPointer != null && myPointer.health >= 100)
{
  // If myPointer is null, the check stops there, and the second
  // condition is never reached, so myPointer.health will never
  // cause a VM abort in this case.
}

2. As far as order goes, normally AND checks are executed before OR checks, but it is highly recommended to use parentheses to avoid any ambiguity. For example:

// Ambiguous:
if (<condition1> && <condition2> || <condition3>)
{ }

// Unambiguous:
if ( (<condition1> && <condition2>) || <condition3>)
{
  // Executes if both condition 1 and 2 are true,
  // OR if condition 3 is true (regardless of the
  // values of condition 1 and 2)
}

// Unambiguous:
if (<condition1> && (<condition2> || <condition3>) )
{
  // Executes if condition 1 is true, AND either 2 or
  // 3 are true.
}

Bitwise operators

& (bitwise AND)

Usually, used to check if a specific value is present in the bit field:

if (myBitField & foo)
{
  // True if foo is contained in myBitField
}

Aside from being used with bit fields, this can also be used with integer fields to make a condition that switches between true and false every set number. For exampe:

override void Tick()
{
  Super.Tick();

  if (GetAge() & 8)
  {
    // this will be true for 8 tics
  }
  else
  {
    // then this will true for the next 8 tics
  }
}

(See Tick and GetAge)

For example, this is how powerups, when about to run out, blink on and off every 8 tics; see IsBlinking.

&= (bitwise AND assignment)

Usually, used in combination with ~ to remove a value from the bitfield:

myBitField &= ~foo; //foo is removed from myBitField

| (bitwise OR)

Used to combine multiple values to set them in a bit field:

int myBitField = bitValue1 | bitValue2; //myBitField now contains both values

|= (bitwise OR assignment)

Adds a value to the bit field:

myBitField |= foo; //foo is added to myBitField

~ (bitwise NOT)

When used after &=, removes the value from the bit field:

myBitField &= ~foo; //foo is removed from myBitField

Miscellaneous operators

is (class inheritance)

Used to check if one class is based on another class through inheritance:

if (MyClass is ParentClass)
{
  // True if MyClass inherits from ParentClass
}

This can be called both on class type pointers and class instance pointers. For example:

if (target is 'DoomImp')
{
  // True if the target actor is a DoomImp or based on it
}
if (target.GetClass() is 'DoomImp')
{
  // True if the target's class is DoomImp or based on it
}

See also