Statements
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 and should not be added if nothing must be done if the condition isn't met; the IF block simply won't be executed in this case. 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
}
If a DO block is used before WHILE, the condition will be checked after the execution:
do
{
// Executes first, then checks if the condition below
// is still true. If not, breaks the loop.
} while (<condition>);
Note that the DO block will be executed at least once, whereas WHILE without DO may be not executed at all if the condition is never met.
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 work with dynamic arrays and various iterators (BlockThingsIterator, ThinkerIterator, ActorIterator). Provides a more compact way to iterate over arrays or array-like data structures 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.
Example of a ThinkerIterator that lets you do something with all classes on the map based on Key:
foreach (Key keyitem : ThinkerIterator.Create('Key'))
{
// do something
}
The same approach can be used on other iterators.
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.