Skip to content

Chapter 1.5: Control Flow

Home | << Previous: Modded Classes | Control Flow | Next: String Operations >>


if / else / else if

The if statement evaluates a boolean expression and executes a block of code when the result is true. You can chain conditions with else if and provide a fallback with else.

c
void CheckHealth(PlayerBase player)
{
    float health = player.GetHealth("", "Health");

    if (health > 75)
    {
        Print("Player is healthy");
    }
    else if (health > 25)
    {
        Print("Player is wounded");
    }
    else
    {
        Print("Player is critical");
    }
}

Null checks

In Enforce Script, object references evaluate to false when null. This is the standard way to guard against null access:

c
void ProcessItem(EntityAI item)
{
    if (!item)
        return;

    string name = item.GetType();
    Print("Processing: " + name);
}

Logical operators

Combine conditions with && (AND) and || (OR). Short-circuit evaluation applies: if the left side of && is false, the right side is never evaluated.

c
void CheckPlayerState(PlayerBase player)
{
    if (player && player.IsAlive())
    {
        // Safe -- player is checked for null before calling IsAlive()
        Print("Player is alive");
    }

    if (player.GetHealth("", "Blood") < 3000 || player.GetHealth("", "Health") < 25)
    {
        Print("Player is in danger");
    }
}

PITFALL: Variable redeclaration in else-if blocks

This is one of the most common Enforce Script errors. In most languages, variables declared inside one if branch are independent from variables in a sibling else branch. Not in Enforce Script. Declaring the same variable name in sibling if/else if/else blocks causes a multiple declaration error at compile time.

c
// WRONG -- Compile error!
void BadExample(Object obj)
{
    if (obj.IsKindOf("Car"))
    {
        Car vehicle = Car.Cast(obj);
        vehicle.GetSpeedometer();
    }
    else if (obj.IsKindOf("ItemBase"))
    {
        ItemBase item = ItemBase.Cast(obj);    // OK -- different name
        item.GetQuantity();
    }
    else
    {
        string msg = "Unknown object";         // First declaration of msg
        Print(msg);
    }
}

Wait -- that looks fine, right? The problem occurs when you use the same variable name in two branches:

c
// WRONG -- Compile error: multiple declaration of 'result'
void ProcessObject(Object obj)
{
    if (obj.IsKindOf("Car"))
    {
        string result = "It's a car";
        Print(result);
    }
    else
    {
        string result = "It's something else";  // ERROR! Same name as in the if block
        Print(result);
    }
}

The fix: Declare the variable before the if statement, or use unique names per branch.

c
// CORRECT -- Declare before the if
void ProcessObject(Object obj)
{
    string result;

    if (obj.IsKindOf("Car"))
    {
        result = "It's a car";
    }
    else
    {
        result = "It's something else";
    }

    Print(result);
}

for Loop

The for loop is identical to C-style syntax: initializer, condition, and increment.

c
// Print numbers 0 through 9
void CountToTen()
{
    for (int i = 0; i < 10; i++)
    {
        Print(i);
    }
}

Iterating over an array with for

c
void ListInventory(PlayerBase player)
{
    array<EntityAI> items = new array<EntityAI>;
    player.GetInventory().EnumerateInventory(InventoryTraversalType.PREORDER, items);

    for (int i = 0; i < items.Count(); i++)
    {
        EntityAI item = items.Get(i);
        if (item)
        {
            Print(string.Format("[%1] %2", i, item.GetType()));
        }
    }
}

Nested for loops

c
// Spawn a grid of objects
void SpawnGrid(vector origin, int rows, int cols, float spacing)
{
    for (int r = 0; r < rows; r++)
    {
        for (int c = 0; c < cols; c++)
        {
            vector pos = origin;
            pos[0] = pos[0] + (c * spacing);
            pos[2] = pos[2] + (r * spacing);
            pos[1] = GetGame().SurfaceY(pos[0], pos[2]);

            GetGame().CreateObject("Barrel_Green", pos, false, false, true);
        }
    }
}

Note: Do not redeclare the loop variable i if there is already a variable named i in the enclosing scope. Enforce Script treats this as a multiple declaration error, even in nested scopes.


while Loop

The while loop repeats a block as long as its condition is true. The condition is evaluated before each iteration.

c
// Remove all dead zombies from a tracking list
void CleanupDeadZombies(array<DayZInfected> zombieList)
{
    int i = 0;
    while (i < zombieList.Count())
    {
        EntityAI eai;
        if (Class.CastTo(eai, zombieList.Get(i)) && !eai.IsAlive())
        {
            zombieList.RemoveOrdered(i);
            // Do NOT increment i -- the next element has shifted into this index
        }
        else
        {
            i++;
        }
    }
}

WARNING: There is NO do...while in Enforce Script

The do...while keyword does not exist. The compiler will reject it. If you need a loop that always executes at least once, use the flag pattern described below.

c
// WRONG -- This will NOT compile
do
{
    // body
}
while (someCondition);

Simulating do...while with a Flag

The standard workaround is to use a bool flag that is true on the first iteration:

c
void SimulateDoWhile()
{
    bool first = true;
    int attempts = 0;
    vector spawnPos;

    while (first || !IsPositionSafe(spawnPos))
    {
        first = false;
        attempts++;
        spawnPos = GetRandomPosition();

        if (attempts > 100)
            break;
    }

    Print(string.Format("Found safe position after %1 attempts", attempts));
}

An alternative approach using break:

c
void AlternativeDoWhile()
{
    while (true)
    {
        // Body executes at least once
        DoSomething();

        // Check the exit condition at the END
        if (!ShouldContinue())
            break;
    }
}

foreach

The foreach statement is the cleanest way to iterate over arrays, maps, and static arrays. It comes in two forms.

Simple foreach (value only)

c
void AnnounceItems(array<string> itemNames)
{
    foreach (string name : itemNames)
    {
        Print("Found item: " + name);
    }
}

foreach with index

When iterating over arrays, the first variable receives the index:

c
void ListPlayers(array<Man> players)
{
    foreach (int idx, Man player : players)
    {
        Print(string.Format("Player #%1: %2", idx, player.GetIdentity().GetName()));
    }
}

foreach over maps

For maps, the first variable receives the key and the second receives the value:

c
void PrintScoreboard(map<string, int> scores)
{
    foreach (string playerName, int score : scores)
    {
        Print(string.Format("%1: %2 kills", playerName, score));
    }
}

You can also iterate over maps with just the value:

c
void SumScores(map<string, int> scores)
{
    int total = 0;
    foreach (int score : scores)
    {
        total += score;
    }
    Print("Total kills: " + total);
}

foreach over static arrays

c
void PrintStaticArray()
{
    int numbers[] = {10, 20, 30, 40, 50};

    foreach (int value : numbers)
    {
        Print(value);
    }
}

switch / case

The switch statement matches a value against a list of case labels. It works with int, string, enum values, and constants.

Important: Fall-through behavior (same as C/C++)

Enforce Script switch/case DOES fall through from one case to the next when break is omitted, just like C/C++. Vanilla code intentionally uses fall-through (biossessionservice.c:182 has comment "Intentionally no break, fall through to connecting"). Always use break at the end of every case unless you intentionally want fall-through.

c
void HandleCommand(string command)
{
    switch (command)
    {
        case "heal":
            HealPlayer();
            break;

        case "kill":
            KillPlayer();
            break;

        case "teleport":
            TeleportPlayer();
            break;

        default:
            Print("Unknown command: " + command);
            break;
    }
}

switch with enums

c
enum EDifficulty
{
    EASY = 0,
    MEDIUM,
    HARD
};

void SetDifficulty(EDifficulty difficulty)
{
    float zombieMultiplier;

    switch (difficulty)
    {
        case EDifficulty.EASY:
            zombieMultiplier = 0.5;
            break;

        case EDifficulty.MEDIUM:
            zombieMultiplier = 1.0;
            break;

        case EDifficulty.HARD:
            zombieMultiplier = 2.0;
            break;

        default:
            zombieMultiplier = 1.0;
            break;
    }

    Print(string.Format("Zombie multiplier: %1", zombieMultiplier));
}

switch with integer constants

c
void DescribeWeaponSlot(int slotId)
{
    const int SLOT_SHOULDER = 0;
    const int SLOT_MELEE = 1;
    const int SLOT_PISTOL = 2;

    switch (slotId)
    {
        case SLOT_SHOULDER:
            Print("Primary weapon");
            break;

        case SLOT_MELEE:
            Print("Melee weapon");
            break;

        case SLOT_PISTOL:
            Print("Sidearm");
            break;

        default:
            Print("Unknown slot");
            break;
    }
}

Note: You can stack cases to share a handler, just like in C/C++. An empty case without break falls through to the next case's handler.


break and continue

break

break exits the innermost loop (or switch case) immediately.

c
// Find the first player within 100 meters
void FindNearbyPlayer(vector origin, array<Man> players)
{
    foreach (Man player : players)
    {
        float dist = vector.Distance(origin, player.GetPosition());
        if (dist < 100)
        {
            Print("Found nearby player: " + player.GetIdentity().GetName());
            break; // Stop searching
        }
    }
}

continue

continue skips the rest of the current iteration and jumps to the next one.

c
// Process only alive players
void HealAllPlayers(array<Man> players)
{
    foreach (Man man : players)
    {
        PlayerBase player;
        if (!Class.CastTo(player, man))
            continue; // Not a PlayerBase, skip

        if (!player.IsAlive())
            continue; // Dead, skip

        player.SetHealth("", "Health", 100);
        Print("Healed: " + player.GetIdentity().GetName());
    }
}

Nested loops with break

break only exits the innermost loop. To break out of nested loops, use a flag variable:

c
void FindItemInGrid(array<array<string>> grid, string target)
{
    bool found = false;

    for (int row = 0; row < grid.Count(); row++)
    {
        for (int col = 0; col < grid.Get(row).Count(); col++)
        {
            if (grid.Get(row).Get(col) == target)
            {
                Print(string.Format("Found '%1' at [%2, %3]", target, row, col));
                found = true;
                break; // Only exits inner loop
            }
        }

        if (found)
            break; // Exits outer loop
    }
}

Thread Keyword

Enforce Script has a thread keyword for asynchronous execution:

c
// Declare a threaded function
thread void LongOperation()
{
    // This runs asynchronously
    Sleep(5000);  // Wait 5 seconds without blocking
    Print("Done!");
}

// Call it
thread LongOperation();  // Starts without blocking the caller

Important: thread in Enforce Script is NOT the same as OS threads. It is more like a coroutine --- it runs on the same thread but can yield/sleep without blocking the game. Use CallLater instead of thread for most mod use cases --- it is simpler and more predictable.

Note on Sleep(): Sleep() is an engine built-in (intrinsic) function --- there is no proto declaration for it in the script files. It takes an int parameter in milliseconds and must be called within a threaded context (i.e., a function invoked with the thread keyword). Calling Sleep() outside a threaded context will crash. It is used by COT, VPP, Expansion, and Dabs Framework in their threaded routines.

Thread vs CallLater

FeaturethreadCallLater
Syntaxthread MyFunc();GetGame().GetCallQueue(CALL_CATEGORY_GAMEPLAY).CallLater(this.MyFunc, delayMs, repeat);
Can sleep/yieldYes (Sleep())No (fires once or repeats at interval)
CancellableNo built-in cancelYes (CallQueue.Remove())
Use caseSequential async logic with waitsDelayed or repeated callbacks

For most DayZ modding scenarios, CallLater with a timer is the preferred approach. Reserve thread for cases where you genuinely need sequential logic with intermediate waits (e.g., a multi-step animation sequence).


Common Mistakes

MistakeProblemFix
Using do...whileDoes not exist in Enforce ScriptUse while with a bool first = true flag
Declaring same variable in if and else blocksMultiple declaration errorDeclare the variable before the if
Redeclaring loop variable i in nested scopeMultiple declaration errorUse different names (i, j, k) or declare outside
Forgetting break in switch casesCases fall through without break (same as C/C++)Always add break unless fall-through is intentional
Modifying array while iterating with foreachUndefined behavior, potential crashUse index-based for loop when removing elements
Infinite while loop without breakServer freeze / client hangAlways ensure the condition will eventually be false, or use break

Quick Reference

c
// if / else if / else
if (condition) { } else if (other) { } else { }

// for loop
for (int i = 0; i < count; i++) { }

// while loop
while (condition) { }

// Simulate do...while
bool first = true;
while (first || condition) { first = false; /* body */ }

// foreach (value only)
foreach (Type value : collection) { }

// foreach (index + value)
foreach (int i, Type value : array) { }

// foreach (key + value on map)
foreach (KeyType key, ValueType val : someMap) { }

// switch/case (falls through without break, like C/C++)
switch (value) { case X: /* ... */ break; default: break; }

// thread (coroutine-style async)
thread void MyFunc() { Sleep(1000); }
thread MyFunc();  // non-blocking call

<< 1.4: Modded Classes | Home | 1.6: String Operations >>

Released under CC BY-SA 4.0 | Code examples under MIT License