Chapter 1.10: Enums & Preprocessor#

Home | « Previous: Casting & Reflection | Enums & Preprocessor | Next: Error Handling »


Goal: Understand enum declarations, enum reflection tools, bitflag patterns, constants, and the preprocessor system for conditional compilation.


Table of Contents#


Enum Declaration#

Enums in Enforce Script define named integer constants grouped under a type name. They behave like int under the hood.

Explicit Values#

enum EDamageState
{
    PRISTINE  = 0,
    WORN      = 1,
    DAMAGED   = 2,
    BADLY_DAMAGED = 3,
    RUINED    = 4
};

Implicit Values#

If you omit values, they auto-increment from the previous value (starting at 0):

enum EWeaponMode
{
    SEMI,       // 0
    BURST,      // 1
    AUTO,       // 2
    COUNT       // 3 — common trick to get the total count
};

Enum Inheritance#

Enums can inherit from other enums. Values continue from the last parent value:

enum EBaseColor
{
    RED    = 0,
    GREEN  = 1,
    BLUE   = 2
};

enum EExtendedColor : EBaseColor
{
    YELLOW,   // 3
    CYAN,     // 4
    MAGENTA   // 5
};

All parent values are accessible through the child enum:

int c = EExtendedColor.RED;      // 0 — inherited from EBaseColor
int d = EExtendedColor.YELLOW;   // 3 — defined in EExtendedColor

Note: Enum inheritance is useful for extending vanilla enums in modded code without changing the original.


Using Enums#

Enums act as int — you can assign them to int variables, compare them, and use them in switch statements:

EDamageState state = EDamageState.WORN;

// Compare
if (state == EDamageState.RUINED)
{
    Print("Item is ruined!");
}

// Use in switch
switch (state)
{
    case EDamageState.PRISTINE:
        Print("Perfect condition");
        break;
    case EDamageState.WORN:
        Print("Slightly worn");
        break;
    case EDamageState.DAMAGED:
        Print("Damaged");
        break;
    case EDamageState.BADLY_DAMAGED:
        Print("Badly damaged");
        break;
    case EDamageState.RUINED:
        Print("Ruined!");
        break;
}

// Assign to int
int stateInt = state;  // 1

// Assign from int (no validation — any int value is accepted!)
EDamageState fromInt = 99;  // No error, even though 99 is not a valid enum value

Warning: Enforce Script does not validate enum assignments. Assigning an out-of-range integer to an enum variable compiles and runs without error.


Enum Reflection#

Enforce Script provides built-in functions to convert between enum values and strings.

typename.EnumToString#

Convert an enum value to its name as a string:

EDamageState state = EDamageState.DAMAGED;
string name = typename.EnumToString(EDamageState, state);
Print(name);  // "DAMAGED"

This is invaluable for logging and UI display:

void LogDamageState(EntityAI item, EDamageState state)
{
    string stateName = typename.EnumToString(EDamageState, state);
    Print(item.GetType() + " is " + stateName);
}

typename.StringToEnum#

Convert a string back to an enum value. The function takes 2 parameters and returns int (the enum value, or -1 on failure):

int value = typename.StringToEnum(EDamageState, "RUINED");
Print(value.ToString());  // "4"

This is used when loading enum values from config files or JSON:

// Loading from a config string
string configValue = "BURST";
int modeInt = typename.StringToEnum(EWeaponMode, configValue);
if (modeInt != -1)
{
    EWeaponMode mode = modeInt;
    Print("Loaded weapon mode: " + typename.EnumToString(EWeaponMode, mode));
}

Bitflags Pattern#

Enums with power-of-2 values create bitflags — multiple options combined in a single integer:

enum ESpawnFlags
{
    NONE            = 0,
    PLACE_ON_GROUND = 1,     // 1 << 0
    CREATE_PHYSICS  = 2,     // 1 << 1
    UPDATE_NAVMESH  = 4,     // 1 << 2
    CREATE_LOCAL    = 8,     // 1 << 3
    NO_LIFETIME     = 16     // 1 << 4
};

Combine with bitwise OR, test with bitwise AND:

// Combine flags
int flags = ESpawnFlags.PLACE_ON_GROUND | ESpawnFlags.CREATE_PHYSICS | ESpawnFlags.UPDATE_NAVMESH;

// Test a single flag
if (flags & ESpawnFlags.CREATE_PHYSICS)
{
    Print("Physics will be created");
}

// Remove a flag
flags = flags & ~ESpawnFlags.CREATE_LOCAL;

// Add a flag
flags = flags | ESpawnFlags.NO_LIFETIME;

DayZ uses this pattern extensively for object creation flags (ECE_PLACE_ON_SURFACE, ECE_CREATEPHYSICS, ECE_UPDATEPATHGRAPH, etc.).


Constants#

Use const to declare immutable values. Constants must be initialized at declaration.

// Integer constants
const int MAX_PLAYERS = 60;
const int INVALID_INDEX = -1;

// Float constants
const float GRAVITY = 9.81;
const float SPAWN_RADIUS = 500.0;

// String constants
const string MOD_NAME = "MyMod";
const string CONFIG_PATH = "$profile:MyMod/config.json";
const string LOG_PREFIX = "[MyMod] ";

Constants can be used as switch case values and array sizes:

// Array with const size
const int BUFFER_SIZE = 256;
int buffer[BUFFER_SIZE];

// Switch with const values
const int CMD_HELP = 1;
const int CMD_SPAWN = 2;
const int CMD_TELEPORT = 3;

switch (command)
{
    case CMD_HELP:
        ShowHelp();
        break;
    case CMD_SPAWN:
        SpawnItem();
        break;
    case CMD_TELEPORT:
        TeleportPlayer();
        break;
}

Note: There is no const for reference types (objects). You cannot make an object reference immutable.


Preprocessor Directives#

The Enforce Script preprocessor runs before compilation, enabling conditional code inclusion. It works similarly to C/C++ preprocessor but with fewer features.

#ifdef / #ifndef / #endif#

Conditionally include code based on whether a symbol is defined:

// Include code only if DEVELOPER is defined
#ifdef DEVELOPER
    Print("[DEBUG] Diagnostics enabled");
#endif

// Include code only if a symbol is NOT defined
#ifndef SERVER
    // Client-only code
    CreateClientUI();
#endif

// If-else pattern
#ifdef SERVER
    Print("Running on server");
#else
    Print("Running on client");
#endif

#define#

Define your own symbols (no value — just existence):

#define MY_MOD_DEBUG

#ifdef MY_MOD_DEBUG
    Print("Debug mode active");
#endif

Note: Enforce Script #define only creates existence flags. It does not support macro substitution (no #define MAX_HP 100 — use const instead).

Common Engine Defines#

DayZ provides these built-in defines based on build type and platform:

DefineWhen AvailableUse For
SERVERRunning on dedicated serverServer-only logic
DEVELOPERDeveloper build of DayZDev-only features
DIAG_DEVELOPERDiagnostic buildDiagnostic menus, debug tools
PLATFORM_WINDOWSWindows platformPlatform-specific paths
PLATFORM_XBOXXbox platformConsole-specific UI
PLATFORM_PS4PlayStation platformConsole-specific logic
BUILD_EXPERIMENTALExperimental branchExperimental features
void InitPlatform()
{
    #ifdef PLATFORM_WINDOWS
        Print("Running on Windows");
    #endif

    #ifdef PLATFORM_XBOX
        Print("Running on Xbox");
    #endif

    #ifdef PLATFORM_PS4
        Print("Running on PlayStation");
    #endif
}

Custom Defines via config.cpp#

Mods can define their own symbols in config.cpp using the defines[] array. These are available to all scripts loaded after this mod:

class CfgMods
{
    class MyMod_MissionSystem
    {
        // ...
        defines[] = { "MY_MISSIONS_LOADED" };
        // ...
    };
};

Now other mods can detect whether your missions mod is loaded:

#ifdef MY_MISSIONS_LOADED
    // Missions mod is loaded — use its API
    MyMissionManager.Start();
#else
    // Missions mod is not loaded — skip or use fallback
    Print("Mission system not detected");
#endif

Real-World Examples#

Platform-Specific Code#

string GetSavePath()
{
    #ifdef PLATFORM_WINDOWS
        return "$profile:MyMod/saves/";
    #else
        return "$saves:MyMod/";
    #endif
}

Optional Mod Dependencies#

This is the standard pattern for mods that optionally integrate with other mods:

class MyModManager
{
    void Init()
    {
        Print("[MyMod] Initializing...");

        // Core features always available
        LoadConfig();
        RegisterRPCs();

        // Optional integration with MyFramework
        #ifdef MY_FRAMEWORK
            Print("[MyMod] Framework detected — using unified logging");
            RegisterWithCore();
        #endif

        // Optional integration with Community Framework
        #ifdef JM_CommunityFramework
            GetRPCManager().AddRPC("MyMod", "RPC_Handler", this, 2);
        #endif
    }
}

Debug-Only Diagnostics#

void ProcessAI(DayZInfected zombie)
{
    vector pos = zombie.GetPosition();
    float health = zombie.GetHealth("", "Health");

    // Heavy debug logging — only in diagnostic builds
    #ifdef DIAG_DEVELOPER
        Print(string.Format("[AI] Zombie %1 at %2, HP: %3",
            zombie.GetType(), pos.ToString(), health.ToString()));

        // Draw debug sphere (only works in diag builds)
        Debug.DrawSphere(pos, 1.0, Colors.RED, ShapeFlags.ONCE);
    #endif

    // Actual logic runs in all builds
    if (health <= 0)
    {
        HandleZombieDeath(zombie);
    }
}

Server vs Client Logic#

class MissionHandler
{
    void OnMissionStart()
    {
        #ifdef SERVER
            // Server: load mission data, spawn objects
            LoadMissionData();
            SpawnMissionObjects();
            NotifyAllPlayers();
        #else
            // Client: set up UI, subscribe to events
            CreateMissionHUD();
            RegisterClientRPCs();
        #endif
    }
}

Best Practices#

  • Add a COUNT sentinel value as the last enum entry to easily iterate or validate ranges (e.g., for (int i = 0; i < EMode.COUNT; i++)).
  • Use power-of-2 values for bitflag enums and combine them with |; test with &; remove with & ~FLAG.
  • Use const instead of #define for numeric constants – Enforce Script #define only creates existence flags, not value macros.
  • Define a defines[] array in your mod’s config.cpp to expose cross-mod detection symbols (e.g., "STARDZ_CORE").
  • Always validate enum values loaded from external data (configs, RPCs) – Enforce Script accepts any int as an enum with no range check.

Observed in Real Mods#

Patterns confirmed by studying professional DayZ mod source code.

PatternModDetail
#ifdef for optional mod integrationExpansion / COTChecks #ifdef JM_CF or #ifdef EXPANSIONMOD before calling cross-mod APIs
Bitflag enums for spawn optionsVanilla DayZECE_PLACE_ON_SURFACE, ECE_CREATEPHYSICS etc. combined with | for CreateObjectEx
typename.EnumToString for loggingExpansion / DabsDamage states and event types are logged as readable strings instead of raw ints
defines[] in config.cppStarDZ Core / ExpansionEach mod declares its own symbol so other mods can detect it with #ifdef

Theory vs Practice#

ConceptTheoryReality
Enum assignment validationExpect compiler to reject invalid valuesEDamageState state = 999 compiles fine – no range checking whatsoever
#define MAX_HP 100Works like C/C++ macroEnforce Script #define creates only existence flags; use const int for values
switch case stackingMultiple cases sharing one handlerFall-through works in Enforce Script (same as C/C++) – stack empty cases to share a handler

Common Mistakes#

1. Using enums as validated types#

// PROBLEM — no validation, any int is accepted
EDamageState state = 999;  // Compiles fine, but 999 is not a valid state

// SOLUTION — validate manually when loading from external data
int rawValue = LoadFromConfig();
if (rawValue >= 0 && rawValue <= EDamageState.RUINED)
{
    EDamageState state = rawValue;
}

2. Trying to use #define for value substitution#

// WRONG — Enforce Script #define does NOT support values
#define MAX_HEALTH 100
int hp = MAX_HEALTH;  // Compile error!

// CORRECT — use const instead
const int MAX_HEALTH = 100;
int hp = MAX_HEALTH;

3. Nesting #ifdef incorrectly#

// CORRECT — nested ifdefs are fine
#ifdef SERVER
    #ifdef MY_FRAMEWORK
        MyLog.Info("MyMod", "Server + Core");
    #endif
#endif

// WRONG — missing #endif causes mysterious compile errors
#ifdef SERVER
    DoServerStuff();
// forgot #endif here!

4. Forgetting break when you do NOT want fall-through#

// Enforce Script switch/case DOES fall through when break is omitted (same as C/C++).
// Vanilla code uses intentional fall-through (biossessionservice.c:182).

// Stacking cases to share a handler WORKS:
switch (state)
{
    case EDamageState.PRISTINE:
    case EDamageState.WORN:
        Print("Good condition");  // Reached for BOTH PRISTINE and WORN
        break;
}

// But forgetting break is a bug:
switch (state)
{
    case EDamageState.PRISTINE:
        Print("Pristine");
        // Missing break! Falls through to WORN case
    case EDamageState.WORN:
        Print("Worn");
        break;
}
// If state == PRISTINE, prints BOTH "Pristine" and "Worn"

Summary#

Enums#

FeatureSyntax
Declareenum EName { A = 0, B = 1 };
Implicitenum EName { A, B, C }; (0, 1, 2)
Inheritenum EChild : EParent { D, E };
To stringtypename.EnumToString(EName, value)
From stringint val = typename.StringToEnum(EName, "A")
Bitflag combine`flags = A
Bitflag testif (flags & A)

Preprocessor#

DirectivePurpose
#ifdef SYMBOLCompile if symbol exists
#ifndef SYMBOLCompile if symbol does NOT exist
#elseAlternate branch
#endifEnd conditional block
#define SYMBOLDefine a symbol (no value)

Key Defines#

DefineMeaning
SERVERDedicated server
DEVELOPERDeveloper build
DIAG_DEVELOPERDiagnostic build
PLATFORM_WINDOWSWindows OS
Custom: defines[]Your mod’s config.cpp

PreviousUpNext
1.9 Casting & ReflectionPart 1: Enforce Script1.11 Error Handling