Statements

From ZDoom Wiki
Jump to navigation Jump to search

This page covers the use of statements in ZScript and DECORATE (In DECORATE, they can only be utilized in anonymous functions). Statements can be utilized to handle logic and flow of execution in code blocks. Operators can be utilized in statements.

if and else

IF/ELSE statements are used to conditionally execute some code. These blocks normally utilize logical operators.

int MyFunction()
{
  if (<condition>)
  {
    // Execute this block if <condition> is true
  }
  else
  {
    // Otherwise, execute this block
  }
}

The ELSE block is optional; if omitted, the IF block simply won't be executed if the condition isn't met. The ELSE block can also be skipped if the preceding IF block ends with a return:

int GetActorHealth(Actor who)
{
  // If the 'who' pointer is null, returns -1 and stops execution:
  if (!who)
  {
    return -1;
  }
  // Otherwise returns the health value of pointer 'who':
  return who.health;
}

switch

SWITCH blocks are used to create conditional blocks with multiple possible results. Sometimes, they can be used instead of IF/ELSE blocks. SWITCH blocks can only be applied to integer numbers and names (because they're internally integers), but not strings, pointers or any other types of values.

// Pass an integer value:
switch (myIntegerValue)
{
// Do this if the value is 0:
case 0:
  DoThing1();
  break;
// Do this if the value is 1:
case 1:
  DoThing2();
  break;
// Do this if the value is 3:
case 3:
  DoThing3();
  break;
}
// If the value is different, nothing happens

In a SWITCH block, each CASE block must end with break, unless a fall-through is desired. If there's no break, the code will just fall through to the next CASE, without checking the value:

switch (myIntegerValue)
{
// Do this if the value is 0:
case 0:
  DoThing1();
// Because there's no break, DoThing2()
// will happen both if the value is 0,
// and if it's 1:
case 1:
  DoThing2();
  break;
}
// If the value is different, nothing happens

Default can be used as a sort of "else" block in a SWITCH block: things specified for the default case will happen if the value isn't equal to any other cases:

// Pass an integer value:
switch (myIntegerValue)
{
// Do this if the value is 0:
case 0:
  DoThing1();
  break;
// Do this if the value is 1:
case 1:
  DoThing2();
  break;
// For all other values, do this:
default:
  DoThing3();
  break;
}

Because names are internally numbers, they can also be used in a switch block:

switch (actorptr.GetClassName())
{
case 'Zombieman':
  DoThing1();
  break;
case 'Shotgunguy':
  DoThing2();
  break;
case 'Chaingunguy':
  DoThing3();
  break;
}

Note that this only checks for a specific class type. For an inheritance-respecting check, use the is operator.

for

FOR creates a loop that will proceed until the condition for termination is no longer valid. The basic syntax of a FOR loop is:

for (<initial value>; <value to compare it to>; <what to do with the value at the end of cycle)
{
  // code
}

Common example:

// i starts at 0 at first; at the end of every cycle
// it's incremented by 1. As soon as i reaches the
// value of 10, the loop ends. (The code in it will
// be executed for the last time when i is 9.)
for (int i = 0; i < 10; i++)
{
  // executes 10 times
}

These loops can be terminated early by calling break anywhere inside the loop, or continue to skip the current cycle and go to the next one. Also, calling return will terminate the whole function, and the loop with it.

Note, like with any incrementation and decrementation operators, the counter value can be changed both before and after comparison:

// This works the same way as the example above, however,
// i never reaches the value of 10; once the code executes
// for the final time, it checks that incrementing it
// further would fail the i < 10 condition, so it just ends:
for (int i = 0; i < 10; ++i)
{
  // executes 10 times
}

This difference is only relevant if a pre-existing value is used as a counter (see below), and it's meant to be used after the loop as well. In other cases there's no difference.

Randomization

If there's a need to execute the loop a random number of times, the initial value must be randomized, not the target value. Common example:

// i is set to a value between 7 and 10 at first,
// then decremented by 1 after every cycle:
for (int i = random(7, 10); i > 0; i--)
{
  // executes between 7 and 10 times
}

This is done because the initial value is set once, whereas the target value, if randomized, will be rolled after every cycle, which is less predictable and more computationally expensive.

Passing existing values

The initial value does not have to be declared inside a FOR loop. An existing value can be passed to it and will be modified at every cycle:

for (angle; angle < 360; angle++)
{
  // Will increase the calling actor's angle by 1
  // until it becomes equal to 360
}
for (angle; angle < 360; ++angle)
{
  // Executes the same number of time as the example
  // above, but the final value of angle will not
  // actually reach 360
}

Iterating over arrays

FOR loops are commonly used to iterate over ZScript arrays:

for (int i = 0; i < myArray.Size(); i++)
{
  let arrayItemPointer = myArray[i]; //obtain pointer to the array item in position i
  if (arrayItemPointer) //null-check the pointer
  {
    // do something with the current
    // item in the array
  }
}

Note, in this case the counter (i) must never be below 0 or equal to or larger than the size of the array. As such, if iterating over the array from the end, the initial value must be 1 smaller than the size, and the final value must be 0, to reach the first item:

for (int i = myArray.Size() - 1; i >= 0; i--)
{
  let arrayItemPointer = myArray[i]; //obtain pointer to the array item in position i
  if (arrayItemPointer) //null-check the pointer
  {
    // do something with the current
    // item in the array
  }
}

Note: Iterating over the array from the end must be used if there's a need to call Delete on specific array items. If this is done while iterating from the start, the counter will jump over the next item because the array size changes.

Iterating over linked lists

Linked lists are similar to arrays, however they don't utilize indexes. Instead, each pointer of a linked list has a pointer to the next item in said list. A common example of a linked list is an actor's inventory, which contains instances of the Inventory class. Each actor has an Inv field; in the main actor it pointers to the first Inventory item in their inventory, while in those items their own Inv pointers point to the next item in the same actor's inventory. So, iterating over it can look as follows:

for (let item = <actorpointer>.Inv; item != null; item = item.Inv)
{
    // do something with 'item'
}

Here let item = <actorpointer>.Inv establishes a pointer to the first item in the actor's inventory, item != null null-checks the pointer, and item = item.Inv updates the item pointer to the next item in that list.

while and do/while

WHILE and DO/WHILE repeatedly execute code until the condition is no longer met. In contrast to FOR loops, they don't autoincrement.

while (<condition>)
{
  // Executes until condition becomes false
}

DO can be added before the WHILE block to visualize the code differently. The execution rules do not change:

do
{
  // Executes until condition specified below becomes false
} while (<condition>);

These loops can be terminated early by calling break anywhere inside the loop. Also, calling return will terminate the whole function, and the loop with it.

foreach

Note: This feature is for ZScript only.

A version of the FOR loop that can only work with dynamic arrays. Provides a more compact way to iterate over arrays, without the need to increment/decrement the value:

Array<Actor> things;
foreach (mo : things)
{
  if (mo)
  {
    // Do something
  }
}

A FOR-loop version that does the same thing:

Array<Actor> things;
for (int i = 0; i < things.Size(); i++)
{
  Actor mo = things[i];
  if (mo)
  {
    // Do something
  }
}

Note: When using a foreach loop, the size of the array cannot change, so Delete, Clear, Pop and similar functions cannot be called on array items. If there's a need to delete items from the array, use a FOR loop and iterate from the end of the array.

break

Break terminates the execution of FOR, DO/WHILE and FOREACH loops.

while (<condition>)
{
  // If condition1 is met, the loop is immediaterly terminated:
  if (<condition1>)
  {
    break;
  }
  // Do something
}

continue

Continue is an instruction that can be used in FOR, DO/WHILE and FOREACH loops. It skips the current cycle of the loop and continues to the next one.

for (int i = 0; i < myArray.Size(); i++)
{
  // If myArray[i] item contains a null pointer,
  // don't do anything and continue to the next
  // cycle:
  if (myArray[i] == null)
  {
    continue;
  }
  // do something with myArray[i]
}

return

Return terminates the current function.

In void functions, return is only necessary if there's a need to cut off the execution early:

void MyVoidFunction()
{
  if (<termination condition>)
  {
    return; //execution stops here
  }
  // Otherwise execution continues:
  <more code>
  // return is not necessary here
}

If a function is supposed to return a value, that value must be provided:

int MyIntegerFunction()
{
  <some kind of calculation>
  return someInteger;
}

If the required return value allows it, multiple conditions can be strung together:

bool CheckActorIsAlive(Actor who)
{
  // returns true if pointer 'who' isn't null and their 
  // health is above 0, otherwise returns false:
  return who != null && who.health > 0;
}

Note: return terminates the whole function, even when it's used inside a loop like a FOR loop. If you want to only terminate a loop without stopping the execution of the rest of the function, use break instead. If you want to skip over a cycle in a loop, use continue.

See also